forge-sql-orm 2.0.30 → 2.1.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/README.md +1090 -81
- package/dist/ForgeSQLORM.js +1080 -60
- package/dist/ForgeSQLORM.js.map +1 -1
- package/dist/ForgeSQLORM.mjs +1063 -60
- package/dist/ForgeSQLORM.mjs.map +1 -1
- package/dist/core/ForgeSQLAnalyseOperations.d.ts +1 -1
- package/dist/core/ForgeSQLAnalyseOperations.d.ts.map +1 -1
- package/dist/core/ForgeSQLCacheOperations.d.ts +119 -0
- package/dist/core/ForgeSQLCacheOperations.d.ts.map +1 -0
- package/dist/core/ForgeSQLCrudOperations.d.ts +38 -22
- package/dist/core/ForgeSQLCrudOperations.d.ts.map +1 -1
- package/dist/core/ForgeSQLORM.d.ts +104 -13
- package/dist/core/ForgeSQLORM.d.ts.map +1 -1
- package/dist/core/ForgeSQLQueryBuilder.d.ts +243 -15
- package/dist/core/ForgeSQLQueryBuilder.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/lib/drizzle/extensions/additionalActions.d.ts +42 -0
- package/dist/lib/drizzle/extensions/additionalActions.d.ts.map +1 -0
- package/dist/utils/cacheContextUtils.d.ts +123 -0
- package/dist/utils/cacheContextUtils.d.ts.map +1 -0
- package/dist/utils/cacheUtils.d.ts +56 -0
- package/dist/utils/cacheUtils.d.ts.map +1 -0
- package/dist/utils/sqlUtils.d.ts +8 -0
- package/dist/utils/sqlUtils.d.ts.map +1 -1
- package/dist/webtriggers/clearCacheSchedulerTrigger.d.ts +46 -0
- package/dist/webtriggers/clearCacheSchedulerTrigger.d.ts.map +1 -0
- package/dist/webtriggers/index.d.ts +1 -0
- package/dist/webtriggers/index.d.ts.map +1 -1
- package/package.json +15 -12
- package/src/core/ForgeSQLAnalyseOperations.ts +1 -1
- package/src/core/ForgeSQLCacheOperations.ts +195 -0
- package/src/core/ForgeSQLCrudOperations.ts +49 -40
- package/src/core/ForgeSQLORM.ts +443 -34
- package/src/core/ForgeSQLQueryBuilder.ts +291 -20
- package/src/index.ts +1 -1
- package/src/lib/drizzle/extensions/additionalActions.ts +548 -0
- package/src/lib/drizzle/extensions/types.d.ts +68 -10
- package/src/utils/cacheContextUtils.ts +210 -0
- package/src/utils/cacheUtils.ts +403 -0
- package/src/utils/sqlUtils.ts +16 -0
- package/src/webtriggers/clearCacheSchedulerTrigger.ts +79 -0
- package/src/webtriggers/index.ts +1 -0
- package/dist/lib/drizzle/extensions/selectAliased.d.ts +0 -9
- package/dist/lib/drizzle/extensions/selectAliased.d.ts.map +0 -1
- package/src/lib/drizzle/extensions/selectAliased.ts +0 -72
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { ForgeSqlOrmOptions } from "../core/ForgeSQLQueryBuilder";
|
|
2
|
+
/**
|
|
3
|
+
* Scheduler trigger for clearing expired cache entries.
|
|
4
|
+
*
|
|
5
|
+
* This trigger should be configured as a Forge scheduler to automatically
|
|
6
|
+
* clean up expired cache entries based on their TTL (Time To Live).
|
|
7
|
+
*
|
|
8
|
+
* @param options - Optional ForgeSQL ORM configuration. If not provided,
|
|
9
|
+
* uses default cache settings with cacheEntityName: "cache"
|
|
10
|
+
* @returns Promise that resolves to HTTP response object
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* // In your index.ts
|
|
15
|
+
* import { clearCacheSchedulerTrigger } from "forge-sql-orm";
|
|
16
|
+
*
|
|
17
|
+
* export const clearCache = () => {
|
|
18
|
+
* return clearCacheSchedulerTrigger({
|
|
19
|
+
* cacheEntityName: "cache",
|
|
20
|
+
* logRawSqlQuery: true
|
|
21
|
+
* });
|
|
22
|
+
* };
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```yaml
|
|
27
|
+
* # In manifest.yml
|
|
28
|
+
* scheduledTrigger:
|
|
29
|
+
* - key: clear-cache-trigger
|
|
30
|
+
* function: clearCache
|
|
31
|
+
* interval: fiveMinute
|
|
32
|
+
*
|
|
33
|
+
* function:
|
|
34
|
+
* - key: clearCache
|
|
35
|
+
* handler: index.clearCache
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export declare const clearCacheSchedulerTrigger: (options?: ForgeSqlOrmOptions) => Promise<{
|
|
39
|
+
headers: {
|
|
40
|
+
"Content-Type": string[];
|
|
41
|
+
};
|
|
42
|
+
statusCode: number;
|
|
43
|
+
statusText: string;
|
|
44
|
+
body: string;
|
|
45
|
+
}>;
|
|
46
|
+
//# sourceMappingURL=clearCacheSchedulerTrigger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clearCacheSchedulerTrigger.d.ts","sourceRoot":"","sources":["../../src/webtriggers/clearCacheSchedulerTrigger.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAElE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,eAAO,MAAM,0BAA0B,GAAU,UAAU,kBAAkB;;;;;;;EAuC5E,CAAC"}
|
|
@@ -2,6 +2,7 @@ export * from "./dropMigrationWebTrigger";
|
|
|
2
2
|
export * from "./applyMigrationsWebTrigger";
|
|
3
3
|
export * from "./fetchSchemaWebTrigger";
|
|
4
4
|
export * from "./dropTablesMigrationWebTrigger";
|
|
5
|
+
export * from "./clearCacheSchedulerTrigger";
|
|
5
6
|
export interface TriggerResponse<BODY> {
|
|
6
7
|
body?: BODY;
|
|
7
8
|
headers?: Record<string, string[]>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/webtriggers/index.ts"],"names":[],"mappings":"AAAA,cAAc,2BAA2B,CAAC;AAC1C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,yBAAyB,CAAC;AACxC,cAAc,iCAAiC,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/webtriggers/index.ts"],"names":[],"mappings":"AAAA,cAAc,2BAA2B,CAAC;AAC1C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,yBAAyB,CAAC;AACxC,cAAc,iCAAiC,CAAC;AAChD,cAAc,8BAA8B,CAAC;AAE7C,MAAM,WAAW,eAAe,CAAC,IAAI;IACnC,IAAI,CAAC,EAAE,IAAI,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IACnC,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,eAAO,MAAM,eAAe,GAAI,IAAI,EAAE,YAAY,MAAM,EAAE,MAAM,IAAI,KAAG,eAAe,CAAC,IAAI,CAc1F,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "forge-sql-orm",
|
|
3
|
-
"version": "2.0
|
|
4
|
-
"description": "Drizzle ORM integration for
|
|
3
|
+
"version": "2.1.0",
|
|
4
|
+
"description": "Drizzle ORM integration for Atlassian @forge/sql. Provides a custom driver, schema migration, two levels of caching (local and global via @forge/kvs), optimistic locking, and query analysis.",
|
|
5
5
|
"main": "dist/ForgeSQLORM.js",
|
|
6
6
|
"module": "dist/ForgeSQLORM.mjs",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
@@ -32,23 +32,23 @@
|
|
|
32
32
|
"database"
|
|
33
33
|
],
|
|
34
34
|
"devDependencies": {
|
|
35
|
-
"@eslint/js": "^9.
|
|
35
|
+
"@eslint/js": "^9.35.0",
|
|
36
36
|
"@types/luxon": "^3.7.1",
|
|
37
|
-
"@types/node": "^24.3.
|
|
38
|
-
"@typescript-eslint/eslint-plugin": "^8.
|
|
39
|
-
"@typescript-eslint/parser": "^8.
|
|
37
|
+
"@types/node": "^24.3.3",
|
|
38
|
+
"@typescript-eslint/eslint-plugin": "^8.43.0",
|
|
39
|
+
"@typescript-eslint/parser": "^8.43.0",
|
|
40
40
|
"@vitest/coverage-v8": "^3.2.4",
|
|
41
41
|
"@vitest/ui": "^3.2.4",
|
|
42
|
-
"eslint": "^9.
|
|
42
|
+
"eslint": "^9.35.0",
|
|
43
43
|
"eslint-config-prettier": "^10.1.8",
|
|
44
44
|
"eslint-plugin-import": "^2.32.0",
|
|
45
45
|
"eslint-plugin-vitest": "^0.5.4",
|
|
46
|
-
"knip": "^5.63.
|
|
46
|
+
"knip": "^5.63.1",
|
|
47
47
|
"prettier": "^3.6.2",
|
|
48
48
|
"ts-node": "^10.9.2",
|
|
49
49
|
"typescript": "^5.9.2",
|
|
50
|
-
"uuid": "^
|
|
51
|
-
"vite": "^7.1.
|
|
50
|
+
"uuid": "^13.0.0",
|
|
51
|
+
"vite": "^7.1.5",
|
|
52
52
|
"vitest": "^3.2.4"
|
|
53
53
|
},
|
|
54
54
|
"license": "MIT",
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"test:watch": "vitest --watch",
|
|
60
60
|
"lint": "eslint src --ext .ts,.tsx",
|
|
61
61
|
"lint:fix": "eslint src --ext .ts,.tsx --fix",
|
|
62
|
-
"format": "prettier --write src examples",
|
|
62
|
+
"format": "prettier --write src examples __tests__",
|
|
63
63
|
"clean": "rm -rf dist",
|
|
64
64
|
"build": "npm run clean && vite build && npm run build:types",
|
|
65
65
|
"build:types": "tsc --emitDeclarationOnly",
|
|
@@ -78,7 +78,10 @@
|
|
|
78
78
|
"@forge/sql": "^3.0.5",
|
|
79
79
|
"drizzle-orm": "^0.44.5"
|
|
80
80
|
},
|
|
81
|
+
"optionalDependencies": {
|
|
82
|
+
"@forge/kvs": "^1.0.5"
|
|
83
|
+
},
|
|
81
84
|
"dependencies": {
|
|
82
|
-
"luxon": "^3.7.
|
|
85
|
+
"luxon": "^3.7.2"
|
|
83
86
|
}
|
|
84
87
|
}
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
SlowQueryNormalized,
|
|
7
7
|
} from "./SystemTables";
|
|
8
8
|
import { SqlParameters } from "@forge/sql/out/sql-statement";
|
|
9
|
-
import { AnyMySqlTable } from "drizzle-orm/mysql-core
|
|
9
|
+
import { AnyMySqlTable } from "drizzle-orm/mysql-core";
|
|
10
10
|
import { getTableName } from "drizzle-orm/table";
|
|
11
11
|
import { DateTime } from "luxon";
|
|
12
12
|
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { MySqlSelectDynamic } from "drizzle-orm/mysql-core/query-builders/select.types";
|
|
2
|
+
import { AnyMySqlSelectQueryBuilder, AnyMySqlTable } from "drizzle-orm/mysql-core";
|
|
3
|
+
import { CacheForgeSQL, ForgeSqlOperation, ForgeSqlOrmOptions } from "./ForgeSQLQueryBuilder";
|
|
4
|
+
import { InferInsertModel, Query, SQL } from "drizzle-orm";
|
|
5
|
+
import { clearCache, getFromCache, setCacheResult, clearTablesCache } from "../utils/cacheUtils";
|
|
6
|
+
import { getTableName } from "drizzle-orm/table";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Implementation of cache operations for ForgeSQL ORM.
|
|
10
|
+
* Provides methods for cacheable database operations with automatic cache management.
|
|
11
|
+
*
|
|
12
|
+
* ⚠️ **IMPORTANT**: All modification methods in this class use optimistic locking/versioning
|
|
13
|
+
* through `modifyWithVersioning()` internally. This ensures data consistency and prevents
|
|
14
|
+
* concurrent modification conflicts.
|
|
15
|
+
*/
|
|
16
|
+
export class ForgeSQLCacheOperations implements CacheForgeSQL {
|
|
17
|
+
private readonly options: ForgeSqlOrmOptions;
|
|
18
|
+
private readonly forgeOperations: ForgeSqlOperation;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Creates a new instance of ForgeSQLCacheOperations.
|
|
22
|
+
*
|
|
23
|
+
* @param options - Configuration options for the ORM
|
|
24
|
+
* @param forgeOperations - The ForgeSQL operations instance
|
|
25
|
+
*/
|
|
26
|
+
constructor(options: ForgeSqlOrmOptions, forgeOperations: ForgeSqlOperation) {
|
|
27
|
+
this.options = options;
|
|
28
|
+
this.forgeOperations = forgeOperations;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Evicts cache for multiple tables using Drizzle table objects.
|
|
33
|
+
*
|
|
34
|
+
* @param tables - Array of Drizzle table objects to clear cache for
|
|
35
|
+
* @returns Promise that resolves when cache eviction is complete
|
|
36
|
+
* @throws Error if cacheEntityName is not configured
|
|
37
|
+
*/
|
|
38
|
+
async evictCacheEntities(tables: AnyMySqlTable[]): Promise<void> {
|
|
39
|
+
if (!this.options.cacheEntityName) {
|
|
40
|
+
throw new Error("cacheEntityName is not configured");
|
|
41
|
+
}
|
|
42
|
+
await this.evictCache(tables.map((t) => getTableName(t)));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Evicts cache for multiple tables by their names.
|
|
47
|
+
*
|
|
48
|
+
* @param tables - Array of table names to clear cache for
|
|
49
|
+
* @returns Promise that resolves when cache eviction is complete
|
|
50
|
+
* @throws Error if cacheEntityName is not configured
|
|
51
|
+
*/
|
|
52
|
+
async evictCache(tables: string[]): Promise<void> {
|
|
53
|
+
if (!this.options.cacheEntityName) {
|
|
54
|
+
throw new Error("cacheEntityName is not configured");
|
|
55
|
+
}
|
|
56
|
+
await clearTablesCache(tables, this.options);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Inserts records with optimistic locking/versioning and automatically evicts cache.
|
|
61
|
+
*
|
|
62
|
+
* This method uses `modifyWithVersioning().insert()` internally, providing:
|
|
63
|
+
* - Automatic version field initialization
|
|
64
|
+
* - Optimistic locking support
|
|
65
|
+
* - Cache eviction after successful operation
|
|
66
|
+
*
|
|
67
|
+
* @param schema - The table schema
|
|
68
|
+
* @param models - Array of entities to insert
|
|
69
|
+
* @param updateIfExists - Whether to update existing records
|
|
70
|
+
* @returns Promise that resolves to the number of inserted rows
|
|
71
|
+
* @throws Error if cacheEntityName is not configured
|
|
72
|
+
* @throws Error if optimistic locking check fails
|
|
73
|
+
*/
|
|
74
|
+
async insert<T extends AnyMySqlTable>(
|
|
75
|
+
schema: T,
|
|
76
|
+
models: InferInsertModel<T>[],
|
|
77
|
+
updateIfExists?: boolean,
|
|
78
|
+
): Promise<number> {
|
|
79
|
+
this.validateCacheConfiguration();
|
|
80
|
+
const number = await this.forgeOperations
|
|
81
|
+
.modifyWithVersioning()
|
|
82
|
+
.insert(schema, models, updateIfExists);
|
|
83
|
+
await clearCache(schema, this.options);
|
|
84
|
+
return number;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Deletes a record by ID with optimistic locking/versioning and automatically evicts cache.
|
|
89
|
+
*
|
|
90
|
+
* This method uses `modifyWithVersioning().deleteById()` internally, providing:
|
|
91
|
+
* - Optimistic locking checks before deletion
|
|
92
|
+
* - Version field validation
|
|
93
|
+
* - Cache eviction after successful operation
|
|
94
|
+
*
|
|
95
|
+
* @param id - The ID of the record to delete
|
|
96
|
+
* @param schema - The table schema
|
|
97
|
+
* @returns Promise that resolves to the number of affected rows
|
|
98
|
+
* @throws Error if cacheEntityName is not configured
|
|
99
|
+
* @throws Error if optimistic locking check fails
|
|
100
|
+
*/
|
|
101
|
+
async deleteById<T extends AnyMySqlTable>(id: unknown, schema: T): Promise<number> {
|
|
102
|
+
this.validateCacheConfiguration();
|
|
103
|
+
const number = await this.forgeOperations.modifyWithVersioning().deleteById(id, schema);
|
|
104
|
+
await clearCache(schema, this.options);
|
|
105
|
+
return number;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Updates a record by ID with optimistic locking/versioning and automatically evicts cache.
|
|
110
|
+
*
|
|
111
|
+
* This method uses `modifyWithVersioning().updateById()` internally, providing:
|
|
112
|
+
* - Optimistic locking checks before update
|
|
113
|
+
* - Version field incrementation
|
|
114
|
+
* - Cache eviction after successful operation
|
|
115
|
+
*
|
|
116
|
+
* @param entity - The entity with updated values (must include primary key)
|
|
117
|
+
* @param schema - The table schema
|
|
118
|
+
* @returns Promise that resolves to the number of affected rows
|
|
119
|
+
* @throws Error if cacheEntityName is not configured
|
|
120
|
+
* @throws Error if optimistic locking check fails
|
|
121
|
+
*/
|
|
122
|
+
async updateById<T extends AnyMySqlTable>(
|
|
123
|
+
entity: Partial<InferInsertModel<T>>,
|
|
124
|
+
schema: T,
|
|
125
|
+
): Promise<number> {
|
|
126
|
+
this.validateCacheConfiguration();
|
|
127
|
+
const number = await this.forgeOperations.modifyWithVersioning().updateById(entity, schema);
|
|
128
|
+
await clearCache(schema, this.options);
|
|
129
|
+
return number;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Updates fields based on conditions with optimistic locking/versioning and automatically evicts cache.
|
|
134
|
+
*
|
|
135
|
+
* This method uses `modifyWithVersioning().updateFields()` internally, providing:
|
|
136
|
+
* - Optimistic locking support (if version field is configured)
|
|
137
|
+
* - Version field validation and incrementation
|
|
138
|
+
* - Cache eviction after successful operation
|
|
139
|
+
*
|
|
140
|
+
* @param updateData - The data to update
|
|
141
|
+
* @param schema - The table schema
|
|
142
|
+
* @param where - Optional WHERE conditions
|
|
143
|
+
* @returns Promise that resolves to the number of affected rows
|
|
144
|
+
* @throws Error if cacheEntityName is not configured
|
|
145
|
+
* @throws Error if optimistic locking check fails
|
|
146
|
+
*/
|
|
147
|
+
async updateFields<T extends AnyMySqlTable>(
|
|
148
|
+
updateData: Partial<InferInsertModel<T>>,
|
|
149
|
+
schema: T,
|
|
150
|
+
where?: SQL<unknown>,
|
|
151
|
+
): Promise<number> {
|
|
152
|
+
this.validateCacheConfiguration();
|
|
153
|
+
const number = await this.forgeOperations
|
|
154
|
+
.modifyWithVersioning()
|
|
155
|
+
.updateFields(updateData, schema, where);
|
|
156
|
+
await clearCache(schema, this.options);
|
|
157
|
+
return number;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Executes a query with caching support.
|
|
162
|
+
* First checks cache, if not found executes query and stores result in cache.
|
|
163
|
+
*
|
|
164
|
+
* @param query - The Drizzle query to execute
|
|
165
|
+
* @param cacheTtl - Optional cache TTL override
|
|
166
|
+
* @returns Promise that resolves to the query results
|
|
167
|
+
* @throws Error if cacheEntityName is not configured
|
|
168
|
+
*/
|
|
169
|
+
async executeQuery<T extends MySqlSelectDynamic<AnyMySqlSelectQueryBuilder>>(
|
|
170
|
+
query: T,
|
|
171
|
+
cacheTtl?: number,
|
|
172
|
+
): Promise<Awaited<T>> {
|
|
173
|
+
this.validateCacheConfiguration();
|
|
174
|
+
const sqlQuery = query as { toSQL: () => Query };
|
|
175
|
+
const cacheResult = await getFromCache<Awaited<T>>(sqlQuery, this.options);
|
|
176
|
+
if (cacheResult) {
|
|
177
|
+
return cacheResult;
|
|
178
|
+
}
|
|
179
|
+
const results = await query;
|
|
180
|
+
await setCacheResult(sqlQuery, this.options, results, cacheTtl ?? this.options.cacheTTL ?? 60);
|
|
181
|
+
return results;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Validates that cache configuration is properly set up.
|
|
186
|
+
*
|
|
187
|
+
* @throws Error if cacheEntityName is not configured
|
|
188
|
+
* @private
|
|
189
|
+
*/
|
|
190
|
+
private validateCacheConfiguration(): void {
|
|
191
|
+
if (!this.options.cacheEntityName) {
|
|
192
|
+
throw new Error("cacheEntityName is not configured");
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
import { ForgeSqlOrmOptions } from "..";
|
|
2
|
-
import {
|
|
3
|
-
import { AnyMySqlTable } from "drizzle-orm/mysql-core
|
|
4
|
-
import { AnyColumn, InferInsertModel } from "drizzle-orm";
|
|
5
|
-
import { eq, and } from "drizzle-orm";
|
|
6
|
-
import { SQL } from "drizzle-orm";
|
|
2
|
+
import { VerioningModificationForgeSQL, ForgeSqlOperation } from "./ForgeSQLQueryBuilder";
|
|
3
|
+
import { AnyMySqlTable } from "drizzle-orm/mysql-core";
|
|
4
|
+
import { and, AnyColumn, eq, InferInsertModel, SQL } from "drizzle-orm";
|
|
7
5
|
import { getPrimaryKeys, getTableMetadata } from "../utils/sqlUtils";
|
|
6
|
+
import { saveTableIfInsideCacheContext } from "../utils/cacheContextUtils";
|
|
8
7
|
|
|
9
8
|
/**
|
|
10
|
-
* Class implementing
|
|
9
|
+
* Class implementing Modification operations for ForgeSQL ORM.
|
|
11
10
|
* Provides methods for inserting, updating, and deleting records with support for optimistic locking.
|
|
12
11
|
*/
|
|
13
|
-
export class ForgeSQLCrudOperations implements
|
|
12
|
+
export class ForgeSQLCrudOperations implements VerioningModificationForgeSQL {
|
|
14
13
|
private readonly forgeOperations: ForgeSqlOperation;
|
|
15
14
|
private readonly options: ForgeSqlOrmOptions;
|
|
16
15
|
|
|
@@ -28,12 +27,17 @@ export class ForgeSQLCrudOperations implements CRUDForgeSQL {
|
|
|
28
27
|
* Inserts records into the database with optional versioning support.
|
|
29
28
|
* If a version field exists in the schema, versioning is applied.
|
|
30
29
|
*
|
|
30
|
+
* This method automatically handles:
|
|
31
|
+
* - Version field initialization for optimistic locking
|
|
32
|
+
* - Batch insertion for multiple records
|
|
33
|
+
* - Duplicate key handling with optional updates
|
|
34
|
+
*
|
|
31
35
|
* @template T - The type of the table schema
|
|
32
|
-
* @param
|
|
33
|
-
* @param
|
|
34
|
-
* @param
|
|
35
|
-
* @returns
|
|
36
|
-
* @throws
|
|
36
|
+
* @param schema - The entity schema
|
|
37
|
+
* @param models - Array of entities to insert
|
|
38
|
+
* @param updateIfExists - Whether to update existing records (default: false)
|
|
39
|
+
* @returns Promise that resolves to the number of inserted rows
|
|
40
|
+
* @throws Error if the insert operation fails
|
|
37
41
|
*/
|
|
38
42
|
async insert<T extends AnyMySqlTable>(
|
|
39
43
|
schema: T,
|
|
@@ -51,10 +55,7 @@ export class ForgeSQLCrudOperations implements CRUDForgeSQL {
|
|
|
51
55
|
);
|
|
52
56
|
|
|
53
57
|
// Build insert query
|
|
54
|
-
const queryBuilder = this.forgeOperations
|
|
55
|
-
.getDrizzleQueryBuilder()
|
|
56
|
-
.insert(schema)
|
|
57
|
-
.values(preparedModels);
|
|
58
|
+
const queryBuilder = this.forgeOperations.insert(schema).values(preparedModels);
|
|
58
59
|
|
|
59
60
|
// Add onDuplicateKeyUpdate if needed
|
|
60
61
|
const finalQuery = updateIfExists
|
|
@@ -67,6 +68,7 @@ export class ForgeSQLCrudOperations implements CRUDForgeSQL {
|
|
|
67
68
|
|
|
68
69
|
// Execute query
|
|
69
70
|
const result = await finalQuery;
|
|
71
|
+
await saveTableIfInsideCacheContext(schema);
|
|
70
72
|
return result[0].insertId;
|
|
71
73
|
}
|
|
72
74
|
|
|
@@ -74,12 +76,18 @@ export class ForgeSQLCrudOperations implements CRUDForgeSQL {
|
|
|
74
76
|
* Deletes a record by its primary key with optional version check.
|
|
75
77
|
* If versioning is enabled, ensures the record hasn't been modified since last read.
|
|
76
78
|
*
|
|
79
|
+
* This method automatically handles:
|
|
80
|
+
* - Single primary key validation
|
|
81
|
+
* - Optimistic locking checks if versioning is enabled
|
|
82
|
+
* - Version field validation before deletion
|
|
83
|
+
*
|
|
77
84
|
* @template T - The type of the table schema
|
|
78
|
-
* @param
|
|
79
|
-
* @param
|
|
80
|
-
* @returns
|
|
81
|
-
* @throws
|
|
82
|
-
* @throws
|
|
85
|
+
* @param id - The ID of the record to delete
|
|
86
|
+
* @param schema - The entity schema
|
|
87
|
+
* @returns Promise that resolves to the number of affected rows
|
|
88
|
+
* @throws Error if the delete operation fails
|
|
89
|
+
* @throws Error if multiple primary keys are found
|
|
90
|
+
* @throws Error if optimistic locking check fails
|
|
83
91
|
*/
|
|
84
92
|
async deleteById<T extends AnyMySqlTable>(id: unknown, schema: T): Promise<number> {
|
|
85
93
|
const { tableName, columns } = getTableMetadata(schema);
|
|
@@ -108,13 +116,13 @@ export class ForgeSQLCrudOperations implements CRUDForgeSQL {
|
|
|
108
116
|
}
|
|
109
117
|
|
|
110
118
|
// Execute delete query
|
|
111
|
-
const queryBuilder = this.forgeOperations
|
|
112
|
-
.getDrizzleQueryBuilder()
|
|
113
|
-
.delete(schema)
|
|
114
|
-
.where(and(...conditions));
|
|
119
|
+
const queryBuilder = this.forgeOperations.delete(schema).where(and(...conditions));
|
|
115
120
|
|
|
116
121
|
const result = await queryBuilder;
|
|
117
|
-
|
|
122
|
+
if (versionMetadata && result[0].affectedRows === 0) {
|
|
123
|
+
throw new Error(`Optimistic locking failed: record with primary key ${id} has been modified`);
|
|
124
|
+
}
|
|
125
|
+
await saveTableIfInsideCacheContext(schema);
|
|
118
126
|
return result[0].affectedRows;
|
|
119
127
|
}
|
|
120
128
|
|
|
@@ -125,13 +133,19 @@ export class ForgeSQLCrudOperations implements CRUDForgeSQL {
|
|
|
125
133
|
* - Checks for concurrent modifications
|
|
126
134
|
* - Increments the version on successful update
|
|
127
135
|
*
|
|
136
|
+
* This method automatically handles:
|
|
137
|
+
* - Primary key validation
|
|
138
|
+
* - Version field retrieval and validation
|
|
139
|
+
* - Optimistic locking conflict detection
|
|
140
|
+
* - Version field incrementation
|
|
141
|
+
*
|
|
128
142
|
* @template T - The type of the table schema
|
|
129
|
-
* @param
|
|
130
|
-
* @param
|
|
131
|
-
* @returns
|
|
132
|
-
* @throws
|
|
133
|
-
* @throws
|
|
134
|
-
* @throws
|
|
143
|
+
* @param entity - The entity with updated values (must include primary key)
|
|
144
|
+
* @param schema - The entity schema
|
|
145
|
+
* @returns Promise that resolves to the number of affected rows
|
|
146
|
+
* @throws Error if the primary key is not provided
|
|
147
|
+
* @throws Error if optimistic locking check fails
|
|
148
|
+
* @throws Error if multiple primary keys are found
|
|
135
149
|
*/
|
|
136
150
|
async updateById<T extends AnyMySqlTable>(
|
|
137
151
|
entity: Partial<InferInsertModel<T>>,
|
|
@@ -177,7 +191,6 @@ export class ForgeSQLCrudOperations implements CRUDForgeSQL {
|
|
|
177
191
|
|
|
178
192
|
// Execute update query
|
|
179
193
|
const queryBuilder = this.forgeOperations
|
|
180
|
-
.getDrizzleQueryBuilder()
|
|
181
194
|
.update(schema)
|
|
182
195
|
.set(updateData)
|
|
183
196
|
.where(and(...conditions));
|
|
@@ -189,7 +202,7 @@ export class ForgeSQLCrudOperations implements CRUDForgeSQL {
|
|
|
189
202
|
`Optimistic locking failed: record with primary key ${entity[primaryKeyName as keyof typeof entity]} has been modified`,
|
|
190
203
|
);
|
|
191
204
|
}
|
|
192
|
-
|
|
205
|
+
await saveTableIfInsideCacheContext(schema);
|
|
193
206
|
return result[0].affectedRows;
|
|
194
207
|
}
|
|
195
208
|
|
|
@@ -214,13 +227,10 @@ export class ForgeSQLCrudOperations implements CRUDForgeSQL {
|
|
|
214
227
|
throw new Error("WHERE conditions must be provided");
|
|
215
228
|
}
|
|
216
229
|
|
|
217
|
-
const queryBuilder = this.forgeOperations
|
|
218
|
-
.getDrizzleQueryBuilder()
|
|
219
|
-
.update(schema)
|
|
220
|
-
.set(updateData)
|
|
221
|
-
.where(where);
|
|
230
|
+
const queryBuilder = this.forgeOperations.update(schema).set(updateData).where(where);
|
|
222
231
|
|
|
223
232
|
const result = await queryBuilder;
|
|
233
|
+
await saveTableIfInsideCacheContext(schema);
|
|
224
234
|
return result[0].affectedRows;
|
|
225
235
|
}
|
|
226
236
|
|
|
@@ -421,7 +431,6 @@ export class ForgeSQLCrudOperations implements CRUDForgeSQL {
|
|
|
421
431
|
const [primaryKeyName, primaryKeyColumn] = primaryKeys[0];
|
|
422
432
|
|
|
423
433
|
const resultQuery = this.forgeOperations
|
|
424
|
-
.getDrizzleQueryBuilder()
|
|
425
434
|
.select({
|
|
426
435
|
[primaryKeyName]: primaryKeyColumn as any,
|
|
427
436
|
[versionFieldName]: versionFieldColumn as any,
|