forge-sql-orm 1.1.31 → 2.0.1
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 +189 -43
- package/dist/ForgeSQLORM.js +81 -161
- package/dist/ForgeSQLORM.js.map +1 -1
- package/dist/ForgeSQLORM.mjs +68 -148
- package/dist/ForgeSQLORM.mjs.map +1 -1
- package/dist/core/ForgeSQLCrudOperations.d.ts.map +1 -1
- package/dist/core/ForgeSQLORM.d.ts +1 -2
- package/dist/core/ForgeSQLORM.d.ts.map +1 -1
- package/dist/core/ForgeSQLQueryBuilder.d.ts +6 -8
- package/dist/core/ForgeSQLQueryBuilder.d.ts.map +1 -1
- package/dist/core/ForgeSQLSelectOperations.d.ts +0 -38
- package/dist/core/ForgeSQLSelectOperations.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/utils/forgeDriver.d.ts +3 -0
- package/dist/utils/forgeDriver.d.ts.map +1 -0
- package/dist/utils/sqlUtils.d.ts +2 -2
- package/dist/utils/sqlUtils.d.ts.map +1 -1
- package/dist-cli/cli.js +28 -38
- package/dist-cli/cli.js.map +1 -1
- package/dist-cli/cli.mjs +24 -34
- package/dist-cli/cli.mjs.map +1 -1
- package/package.json +8 -10
- package/src/core/ForgeSQLCrudOperations.ts +11 -19
- package/src/core/ForgeSQLORM.ts +7 -8
- package/src/core/ForgeSQLQueryBuilder.ts +6 -11
- package/src/core/ForgeSQLSelectOperations.ts +4 -132
- package/src/index.ts +1 -0
- package/src/utils/forgeDriver.ts +37 -0
- package/src/utils/sqlUtils.ts +48 -38
package/README.md
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
**Forge-SQL-ORM** is an ORM designed for working with [@forge/sql](https://developer.atlassian.com/platform/forge/storage-reference/sql-tutorial/) in **Atlassian Forge**. It is built on top of [Drizzle ORM](https://orm.drizzle.team) and provides advanced capabilities for working with relational databases inside Forge.
|
|
6
6
|
|
|
7
7
|
## Key Features
|
|
8
|
+
- ✅ **Custom Drizzle Driver** for direct integration with @forge/sql
|
|
8
9
|
- ✅ **Supports complex SQL queries** with joins and filtering using Drizzle ORM
|
|
9
10
|
- ✅ **Batch insert support** with duplicate key handling
|
|
10
11
|
- ✅ **Schema migration support**, allowing automatic schema evolution
|
|
@@ -14,6 +15,23 @@
|
|
|
14
15
|
- ✅ **Optimistic Locking** Ensures data consistency by preventing conflicts when multiple users update the same record
|
|
15
16
|
- ✅ **Type Safety** Full TypeScript support with proper type inference
|
|
16
17
|
|
|
18
|
+
## Usage Approaches
|
|
19
|
+
|
|
20
|
+
### 1. Direct Drizzle Usage
|
|
21
|
+
```typescript
|
|
22
|
+
import { drizzle } from "drizzle-orm/mysql-core";
|
|
23
|
+
import { forgeDriver } from "forge-sql-orm";
|
|
24
|
+
const db = drizzle(forgeDriver);
|
|
25
|
+
```
|
|
26
|
+
Best for: Simple CRUD operations without optimistic locking
|
|
27
|
+
|
|
28
|
+
### 2. Full Forge-SQL-ORM Usage
|
|
29
|
+
```typescript
|
|
30
|
+
import ForgeSQL from "forge-sql-orm";
|
|
31
|
+
const forgeSQL = new ForgeSQL();
|
|
32
|
+
```
|
|
33
|
+
Best for: Advanced features like optimistic locking and automatic versioning
|
|
34
|
+
|
|
17
35
|
## Installation
|
|
18
36
|
|
|
19
37
|
Forge-SQL-ORM is designed to work with @forge/sql and requires some additional setup to ensure compatibility within Atlassian Forge.
|
|
@@ -22,8 +40,7 @@ Forge-SQL-ORM is designed to work with @forge/sql and requires some additional s
|
|
|
22
40
|
|
|
23
41
|
```sh
|
|
24
42
|
npm install forge-sql-orm -S
|
|
25
|
-
npm install @forge/sql -S
|
|
26
|
-
npm install drizzle-orm mysql2
|
|
43
|
+
npm install @forge/sql drizzle-orm -S
|
|
27
44
|
npm install mysql2 @types/mysql2 -D
|
|
28
45
|
```
|
|
29
46
|
|
|
@@ -33,6 +50,38 @@ This will:
|
|
|
33
50
|
- Install Drizzle ORM and its MySQL driver
|
|
34
51
|
- Install TypeScript types for MySQL
|
|
35
52
|
|
|
53
|
+
## Direct Drizzle Usage with Custom Driver
|
|
54
|
+
|
|
55
|
+
If you prefer to use Drizzle ORM directly without the additional features of Forge-SQL-ORM (like optimistic locking), you can use the custom driver:
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
import { drizzle } from "drizzle-orm/mysql-core";
|
|
59
|
+
import { forgeDriver } from "forge-sql-orm";
|
|
60
|
+
|
|
61
|
+
// Initialize drizzle with the custom driver
|
|
62
|
+
const db = drizzle(forgeDriver);
|
|
63
|
+
|
|
64
|
+
// Use drizzle directly
|
|
65
|
+
const users = await db.select().from(users);
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Drizzle Usage with forge-sql-orm
|
|
69
|
+
|
|
70
|
+
If you prefer to use Drizzle ORM with the additional features of Forge-SQL-ORM (like optimistic locking), you can use the custom driver:
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
import ForgeSQL from "forge-sql-orm";
|
|
74
|
+
const forgeSQL = new ForgeSQL();
|
|
75
|
+
forgeSQL.crud().insert(...);
|
|
76
|
+
forgeSQL.crud().updateById(...);
|
|
77
|
+
const db = forgeSQL.getDrizzleQueryBuilder();
|
|
78
|
+
|
|
79
|
+
// Use drizzle
|
|
80
|
+
const users = await db.select().from(users);
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
This approach gives you direct access to all Drizzle ORM features while still using the @forge/sql backend.
|
|
84
|
+
|
|
36
85
|
## Step-by-Step Migration Workflow
|
|
37
86
|
|
|
38
87
|
1. **Generate initial schema from an existing database**
|
|
@@ -155,6 +204,68 @@ export default (migrationRunner: MigrationRunner): MigrationRunner => {
|
|
|
155
204
|
|
|
156
205
|
---
|
|
157
206
|
|
|
207
|
+
## Date and Time Types
|
|
208
|
+
|
|
209
|
+
When working with date and time fields in your models, you should use the custom types provided by Forge-SQL-ORM to ensure proper handling of date/time values. This is necessary because Forge SQL has specific format requirements for date/time values:
|
|
210
|
+
|
|
211
|
+
| Date type | Required Format | Example |
|
|
212
|
+
|-----------|----------------|---------|
|
|
213
|
+
| DATE | YYYY-MM-DD | 2024-09-19 |
|
|
214
|
+
| TIME | HH:MM:SS[.fraction] | 06:40:34 |
|
|
215
|
+
| TIMESTAMP | YYYY-MM-DD HH:MM:SS[.fraction] | 2024-09-19 06:40:34.999999 |
|
|
216
|
+
|
|
217
|
+
```typescript
|
|
218
|
+
// ❌ Don't use standard Drizzle date/time types
|
|
219
|
+
export const testEntityTimeStampVersion = mysqlTable('test_entity', {
|
|
220
|
+
id: int('id').primaryKey().autoincrement(),
|
|
221
|
+
time_stamp: timestamp('times_tamp').notNull(),
|
|
222
|
+
date_time: datetime('date_time').notNull(),
|
|
223
|
+
time: time('time').notNull(),
|
|
224
|
+
date: date('date').notNull(),
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
// ✅ Use Forge-SQL-ORM custom types instead
|
|
228
|
+
import { forgeDateTimeString, forgeDateString, mySqlTimestampString, mySqlTimeString } from 'forge-sql-orm'
|
|
229
|
+
|
|
230
|
+
export const testEntityTimeStampVersion = mysqlTable('test_entity', {
|
|
231
|
+
id: int('id').primaryKey().autoincrement(),
|
|
232
|
+
time_stamp: forgeTimestampString('times_tamp').notNull(),
|
|
233
|
+
date_time: forgeDateTimeString('date_time').notNull(),
|
|
234
|
+
time: forgeTimeString('time').notNull(),
|
|
235
|
+
date: forgeDateString('date').notNull(),
|
|
236
|
+
});
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### Why Custom Types?
|
|
240
|
+
|
|
241
|
+
The custom types in Forge-SQL-ORM handle the conversion between JavaScript Date objects and Forge SQL's required string formats automatically. Without these custom types, you would need to manually format dates like this:
|
|
242
|
+
|
|
243
|
+
```typescript
|
|
244
|
+
// Without custom types, you'd need to do this manually:
|
|
245
|
+
const date = moment().format("YYYY-MM-DD");
|
|
246
|
+
const time = moment().format("HH:mm:ss.SSS");
|
|
247
|
+
const timestamp = moment().format("YYYY-MM-DDTHH:mm:ss.SSS");
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
Our custom types provide:
|
|
251
|
+
- Automatic conversion between JavaScript Date objects and Forge SQL's required string formats
|
|
252
|
+
- Consistent date/time handling across your application
|
|
253
|
+
- Type safety for date/time fields
|
|
254
|
+
- Proper handling of timezone conversions
|
|
255
|
+
- Support for all Forge SQL date/time types (datetime, timestamp, date, time)
|
|
256
|
+
|
|
257
|
+
### Available Custom Types
|
|
258
|
+
|
|
259
|
+
- `forgeDateTimeString` - For datetime fields (YYYY-MM-DD HH:MM:SS[.fraction])
|
|
260
|
+
- `forgeTimestampString` - For timestamp fields (YYYY-MM-DD HH:MM:SS[.fraction])
|
|
261
|
+
- `forgeDateString` - For date fields (YYYY-MM-DD)
|
|
262
|
+
- `forgeTimeString` - For time fields (HH:MM:SS[.fraction])
|
|
263
|
+
|
|
264
|
+
Each type ensures that the data is properly formatted according to Forge SQL's requirements while providing a clean, type-safe interface for your application code.
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
|
|
158
269
|
# Connection to ORM
|
|
159
270
|
|
|
160
271
|
```js
|
|
@@ -162,20 +273,35 @@ import ForgeSQL from "forge-sql-orm";
|
|
|
162
273
|
|
|
163
274
|
const forgeSQL = new ForgeSQL();
|
|
164
275
|
```
|
|
276
|
+
or
|
|
277
|
+
|
|
278
|
+
```typescript
|
|
279
|
+
import { drizzle } from "drizzle-orm/mysql-core";
|
|
280
|
+
import { forgeDriver } from "forge-sql-orm";
|
|
281
|
+
|
|
282
|
+
// Initialize drizzle with the custom driver
|
|
283
|
+
const db = drizzle(forgeDriver);
|
|
284
|
+
|
|
285
|
+
// Use drizzle directly
|
|
286
|
+
const users = await db.select().from(users);
|
|
287
|
+
```
|
|
165
288
|
|
|
166
289
|
## Fetch Data
|
|
167
290
|
|
|
168
291
|
### Basic Fetch Operations
|
|
169
292
|
|
|
170
293
|
```js
|
|
171
|
-
// Using
|
|
294
|
+
// Using forgeSQL.getDrizzleQueryBuilder()
|
|
172
295
|
const user = await forgeSQL
|
|
173
|
-
.
|
|
174
|
-
.
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
296
|
+
.getDrizzleQueryBuilder()
|
|
297
|
+
.select("*").from(Users)
|
|
298
|
+
.where(eq(Users.id, 1));
|
|
299
|
+
|
|
300
|
+
// OR using direct drizzle with custom driver
|
|
301
|
+
const db = drizzle(forgeDriver);
|
|
302
|
+
const user = await db
|
|
303
|
+
.select("*").from(Users)
|
|
304
|
+
.where(eq(Users.id, 1));
|
|
179
305
|
// Returns: { id: 1, name: "John Doe" }
|
|
180
306
|
|
|
181
307
|
// Using executeQueryOnlyOne for single result with error handling
|
|
@@ -191,34 +317,47 @@ const user = await forgeSQL
|
|
|
191
317
|
// Throws error if multiple records found
|
|
192
318
|
// Returns undefined if no records found
|
|
193
319
|
|
|
194
|
-
// Using
|
|
320
|
+
// Using with aliases
|
|
321
|
+
// With forgeSQL
|
|
195
322
|
const usersAlias = alias(Users, "u");
|
|
196
323
|
const result = await forgeSQL
|
|
197
|
-
.
|
|
198
|
-
.
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
324
|
+
.getDrizzleQueryBuilder()
|
|
325
|
+
.select({
|
|
326
|
+
userId: rawSql`${usersAlias.id} as \`userId\``,
|
|
327
|
+
userName: rawSql`${usersAlias.name} as \`userName\``
|
|
328
|
+
}).from(usersAlias);
|
|
329
|
+
|
|
330
|
+
// OR with direct drizzle
|
|
331
|
+
const db = drizzle(forgeDriver);
|
|
332
|
+
const result = await db
|
|
333
|
+
.select({
|
|
334
|
+
userId: rawSql`${usersAlias.id} as \`userId\``,
|
|
335
|
+
userName: rawSql`${usersAlias.name} as \`userName\``
|
|
336
|
+
}).from(usersAlias);
|
|
206
337
|
// Returns: { userId: 1, userName: "John Doe" }
|
|
207
338
|
|
|
208
|
-
// Using
|
|
339
|
+
// Using joins
|
|
340
|
+
// With forgeSQL
|
|
209
341
|
const orderWithUser = await forgeSQL
|
|
210
|
-
.
|
|
211
|
-
.
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
342
|
+
.getDrizzleQueryBuilder()
|
|
343
|
+
.select({
|
|
344
|
+
orderId: rawSql`${Orders.id} as \`orderId\``,
|
|
345
|
+
product: Orders.product,
|
|
346
|
+
userName: rawSql`${Users.name} as \`userName\``
|
|
347
|
+
}).from(Orders)
|
|
348
|
+
.innerJoin(Users, eq(Orders.userId, Users.id))
|
|
349
|
+
.where(eq(Orders.id, 1));
|
|
350
|
+
|
|
351
|
+
// OR with direct drizzle
|
|
352
|
+
const db = drizzle(forgeDriver);
|
|
353
|
+
const orderWithUser = await db
|
|
354
|
+
.select({
|
|
355
|
+
orderId: rawSql`${Orders.id} as \`orderId\``,
|
|
356
|
+
product: Orders.product,
|
|
357
|
+
userName: rawSql`${Users.name} as \`userName\``
|
|
358
|
+
}).from(Orders)
|
|
359
|
+
.innerJoin(Users, eq(Orders.userId, Users.id))
|
|
360
|
+
.where(eq(Orders.id, 1));
|
|
222
361
|
// Returns: { orderId: 1, product: "Product 1", userName: "John Doe" }
|
|
223
362
|
```
|
|
224
363
|
|
|
@@ -226,18 +365,25 @@ const orderWithUser = await forgeSQL
|
|
|
226
365
|
|
|
227
366
|
```js
|
|
228
367
|
// Finding duplicates
|
|
368
|
+
// With forgeSQL
|
|
229
369
|
const duplicates = await forgeSQL
|
|
230
|
-
.
|
|
231
|
-
.
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
370
|
+
.getDrizzleQueryBuilder()
|
|
371
|
+
.select({
|
|
372
|
+
name: Users.name,
|
|
373
|
+
count: rawSql`COUNT(*) as \`count\``
|
|
374
|
+
}).from(Users)
|
|
375
|
+
.groupBy(Users.name)
|
|
376
|
+
.having(rawSql`COUNT(*) > 1`);
|
|
377
|
+
|
|
378
|
+
// OR with direct drizzle
|
|
379
|
+
const db = drizzle(forgeDriver);
|
|
380
|
+
const duplicates = await db
|
|
381
|
+
.select({
|
|
382
|
+
name: Users.name,
|
|
383
|
+
count: rawSql`COUNT(*) as \`count\``
|
|
384
|
+
}).from(Users)
|
|
385
|
+
.groupBy(Users.name)
|
|
386
|
+
.having(rawSql`COUNT(*) > 1`);
|
|
241
387
|
// Returns: { name: "John Doe", count: 2 }
|
|
242
388
|
|
|
243
389
|
// Using executeQueryOnlyOne for unique results
|
package/dist/ForgeSQLORM.js
CHANGED
|
@@ -2,11 +2,6 @@
|
|
|
2
2
|
Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: "Module" } });
|
|
3
3
|
const drizzleOrm = require("drizzle-orm");
|
|
4
4
|
const moment = require("moment");
|
|
5
|
-
const primaryKeys = require("drizzle-orm/mysql-core/primary-keys");
|
|
6
|
-
const indexes = require("drizzle-orm/mysql-core/indexes");
|
|
7
|
-
const checks = require("drizzle-orm/mysql-core/checks");
|
|
8
|
-
const foreignKeys = require("drizzle-orm/mysql-core/foreign-keys");
|
|
9
|
-
const uniqueConstraint = require("drizzle-orm/mysql-core/unique-constraint");
|
|
10
5
|
const sql = require("@forge/sql");
|
|
11
6
|
const mysql2 = require("drizzle-orm/mysql2");
|
|
12
7
|
const mysqlCore = require("drizzle-orm/mysql-core");
|
|
@@ -23,31 +18,29 @@ function extractAlias(query) {
|
|
|
23
18
|
return match ? match[2] : query;
|
|
24
19
|
}
|
|
25
20
|
function getPrimaryKeys(table) {
|
|
26
|
-
const { columns, primaryKeys
|
|
21
|
+
const { columns, primaryKeys } = getTableMetadata(table);
|
|
27
22
|
const columnPrimaryKeys = Object.entries(columns).filter(([, column]) => column.primary);
|
|
28
23
|
if (columnPrimaryKeys.length > 0) {
|
|
29
24
|
return columnPrimaryKeys;
|
|
30
25
|
}
|
|
31
|
-
if (Array.isArray(
|
|
26
|
+
if (Array.isArray(primaryKeys) && primaryKeys.length > 0) {
|
|
32
27
|
const primaryKeyColumns = /* @__PURE__ */ new Set();
|
|
33
|
-
|
|
28
|
+
primaryKeys.forEach((primaryKeyBuilder) => {
|
|
34
29
|
Object.entries(columns).filter(([, column]) => {
|
|
35
30
|
return primaryKeyBuilder.columns.includes(column);
|
|
36
31
|
}).forEach(([name, column]) => {
|
|
37
32
|
primaryKeyColumns.add([name, column]);
|
|
38
33
|
});
|
|
39
34
|
});
|
|
40
|
-
|
|
41
|
-
return result.length > 0 ? result : void 0;
|
|
35
|
+
return Array.from(primaryKeyColumns);
|
|
42
36
|
}
|
|
43
|
-
return
|
|
37
|
+
return [];
|
|
44
38
|
}
|
|
45
39
|
function getTableMetadata(table) {
|
|
46
40
|
const symbols = Object.getOwnPropertySymbols(table);
|
|
47
41
|
const nameSymbol = symbols.find((s) => s.toString().includes("Name"));
|
|
48
42
|
const columnsSymbol = symbols.find((s) => s.toString().includes("Columns"));
|
|
49
43
|
const extraSymbol = symbols.find((s) => s.toString().includes("ExtraConfigBuilder"));
|
|
50
|
-
const foreignKeysSymbol = symbols.find((s) => s.toString().includes("MySqlInlineForeignKeys)"));
|
|
51
44
|
const builders = {
|
|
52
45
|
indexes: [],
|
|
53
46
|
checks: [],
|
|
@@ -56,38 +49,33 @@ function getTableMetadata(table) {
|
|
|
56
49
|
uniqueConstraints: [],
|
|
57
50
|
extras: []
|
|
58
51
|
};
|
|
59
|
-
if (foreignKeysSymbol) {
|
|
60
|
-
const foreignKeys2 = table[foreignKeysSymbol];
|
|
61
|
-
if (foreignKeys2) {
|
|
62
|
-
for (const foreignKey of foreignKeys2) {
|
|
63
|
-
builders.foreignKeys.push(foreignKey);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
52
|
if (extraSymbol) {
|
|
68
53
|
const extraConfigBuilder = table[extraSymbol];
|
|
69
54
|
if (extraConfigBuilder && typeof extraConfigBuilder === "function") {
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
55
|
+
const configBuilderData = extraConfigBuilder(table);
|
|
56
|
+
if (configBuilderData) {
|
|
57
|
+
const configBuilders = Array.isArray(configBuilderData) ? configBuilderData : Object.values(configBuilderData).map(
|
|
58
|
+
(item) => item.value || item
|
|
59
|
+
);
|
|
60
|
+
configBuilders.forEach((builder) => {
|
|
61
|
+
if (!builder?.constructor) return;
|
|
62
|
+
const builderName = builder.constructor.name.toLowerCase();
|
|
63
|
+
const builderMap = {
|
|
64
|
+
indexbuilder: builders.indexes,
|
|
65
|
+
checkbuilder: builders.checks,
|
|
66
|
+
foreignkeybuilder: builders.foreignKeys,
|
|
67
|
+
primarykeybuilder: builders.primaryKeys,
|
|
68
|
+
uniqueconstraintbuilder: builders.uniqueConstraints
|
|
69
|
+
};
|
|
70
|
+
for (const [type, array] of Object.entries(builderMap)) {
|
|
71
|
+
if (builderName.includes(type)) {
|
|
72
|
+
array.push(builder);
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
builders.extras.push(builder);
|
|
77
|
+
});
|
|
76
78
|
}
|
|
77
|
-
configBuildersArray.forEach((builder) => {
|
|
78
|
-
if (builder instanceof indexes.IndexBuilder) {
|
|
79
|
-
builders.indexes.push(builder);
|
|
80
|
-
} else if (builder instanceof checks.CheckBuilder) {
|
|
81
|
-
builders.checks.push(builder);
|
|
82
|
-
} else if (builder instanceof foreignKeys.ForeignKeyBuilder) {
|
|
83
|
-
builders.foreignKeys.push(builder);
|
|
84
|
-
} else if (builder instanceof primaryKeys.PrimaryKeyBuilder) {
|
|
85
|
-
builders.primaryKeys.push(builder);
|
|
86
|
-
} else if (builder instanceof uniqueConstraint.UniqueConstraintBuilder) {
|
|
87
|
-
builders.uniqueConstraints.push(builder);
|
|
88
|
-
}
|
|
89
|
-
builders.extras.push(builder);
|
|
90
|
-
});
|
|
91
79
|
}
|
|
92
80
|
}
|
|
93
81
|
return {
|
|
@@ -136,8 +124,8 @@ class ForgeSQLCrudOperations {
|
|
|
136
124
|
if (this.options?.logRawSqlQuery) {
|
|
137
125
|
console.debug("INSERT SQL:", query.sql);
|
|
138
126
|
}
|
|
139
|
-
const result = await
|
|
140
|
-
return result.insertId;
|
|
127
|
+
const result = await finalQuery;
|
|
128
|
+
return result[0].insertId;
|
|
141
129
|
}
|
|
142
130
|
/**
|
|
143
131
|
* Deletes a record by its primary key with optional version check.
|
|
@@ -152,11 +140,11 @@ class ForgeSQLCrudOperations {
|
|
|
152
140
|
*/
|
|
153
141
|
async deleteById(id, schema) {
|
|
154
142
|
const { tableName, columns } = getTableMetadata(schema);
|
|
155
|
-
const
|
|
156
|
-
if (
|
|
143
|
+
const primaryKeys = this.getPrimaryKeys(schema);
|
|
144
|
+
if (primaryKeys.length !== 1) {
|
|
157
145
|
throw new Error("Only single primary key is supported");
|
|
158
146
|
}
|
|
159
|
-
const [primaryKeyName, primaryKeyColumn] =
|
|
147
|
+
const [primaryKeyName, primaryKeyColumn] = primaryKeys[0];
|
|
160
148
|
const versionMetadata = this.validateVersionField(tableName, columns);
|
|
161
149
|
const conditions = [drizzleOrm.eq(primaryKeyColumn, id)];
|
|
162
150
|
if (versionMetadata && columns) {
|
|
@@ -173,8 +161,8 @@ class ForgeSQLCrudOperations {
|
|
|
173
161
|
if (this.options?.logRawSqlQuery) {
|
|
174
162
|
console.debug("DELETE SQL:", queryBuilder.toSQL().sql);
|
|
175
163
|
}
|
|
176
|
-
const result = await
|
|
177
|
-
return result.affectedRows;
|
|
164
|
+
const result = await queryBuilder;
|
|
165
|
+
return result[0].affectedRows;
|
|
178
166
|
}
|
|
179
167
|
/**
|
|
180
168
|
* Updates a record by its primary key with optimistic locking support.
|
|
@@ -193,11 +181,11 @@ class ForgeSQLCrudOperations {
|
|
|
193
181
|
*/
|
|
194
182
|
async updateById(entity, schema) {
|
|
195
183
|
const { tableName, columns } = getTableMetadata(schema);
|
|
196
|
-
const
|
|
197
|
-
if (
|
|
184
|
+
const primaryKeys = this.getPrimaryKeys(schema);
|
|
185
|
+
if (primaryKeys.length !== 1) {
|
|
198
186
|
throw new Error("Only single primary key is supported");
|
|
199
187
|
}
|
|
200
|
-
const [primaryKeyName, primaryKeyColumn] =
|
|
188
|
+
const [primaryKeyName, primaryKeyColumn] = primaryKeys[0];
|
|
201
189
|
const versionMetadata = this.validateVersionField(tableName, columns);
|
|
202
190
|
if (!(primaryKeyName in entity)) {
|
|
203
191
|
throw new Error(`Primary key ${primaryKeyName} must be provided in the entity`);
|
|
@@ -223,13 +211,13 @@ class ForgeSQLCrudOperations {
|
|
|
223
211
|
if (this.options?.logRawSqlQuery) {
|
|
224
212
|
console.debug("UPDATE SQL:", queryBuilder.toSQL().sql);
|
|
225
213
|
}
|
|
226
|
-
const result = await
|
|
227
|
-
if (versionMetadata && result.affectedRows === 0) {
|
|
214
|
+
const result = await queryBuilder;
|
|
215
|
+
if (versionMetadata && result[0].affectedRows === 0) {
|
|
228
216
|
throw new Error(
|
|
229
217
|
`Optimistic locking failed: record with primary key ${entity[primaryKeyName]} has been modified`
|
|
230
218
|
);
|
|
231
219
|
}
|
|
232
|
-
return result.affectedRows;
|
|
220
|
+
return result[0].affectedRows;
|
|
233
221
|
}
|
|
234
222
|
/**
|
|
235
223
|
* Updates specified fields of records based on provided conditions.
|
|
@@ -251,8 +239,8 @@ class ForgeSQLCrudOperations {
|
|
|
251
239
|
if (this.options?.logRawSqlQuery) {
|
|
252
240
|
console.debug("UPDATE SQL:", queryBuilder.toSQL().sql);
|
|
253
241
|
}
|
|
254
|
-
const result = await
|
|
255
|
-
return result.affectedRows;
|
|
242
|
+
const result = await queryBuilder;
|
|
243
|
+
return result[0].affectedRows;
|
|
256
244
|
}
|
|
257
245
|
// Helper methods
|
|
258
246
|
/**
|
|
@@ -263,11 +251,11 @@ class ForgeSQLCrudOperations {
|
|
|
263
251
|
* @throws {Error} If no primary keys are found
|
|
264
252
|
*/
|
|
265
253
|
getPrimaryKeys(schema) {
|
|
266
|
-
const
|
|
267
|
-
if (!
|
|
254
|
+
const primaryKeys = getPrimaryKeys(schema);
|
|
255
|
+
if (!primaryKeys) {
|
|
268
256
|
throw new Error(`No primary keys found for schema: ${schema}`);
|
|
269
257
|
}
|
|
270
|
-
return
|
|
258
|
+
return primaryKeys;
|
|
271
259
|
}
|
|
272
260
|
/**
|
|
273
261
|
* Validates and retrieves version field metadata.
|
|
@@ -377,8 +365,8 @@ class ForgeSQLCrudOperations {
|
|
|
377
365
|
*/
|
|
378
366
|
async getOldModel(primaryKeyValues, schema, versionField) {
|
|
379
367
|
const [versionFieldName, versionFieldColumn] = versionField;
|
|
380
|
-
const
|
|
381
|
-
const [primaryKeyName, primaryKeyColumn] =
|
|
368
|
+
const primaryKeys = this.getPrimaryKeys(schema);
|
|
369
|
+
const [primaryKeyName, primaryKeyColumn] = primaryKeys[0];
|
|
382
370
|
const resultQuery = this.forgeOperations.getDrizzleQueryBuilder().select({
|
|
383
371
|
[primaryKeyName]: primaryKeyColumn,
|
|
384
372
|
[versionFieldName]: versionFieldColumn
|
|
@@ -399,105 +387,6 @@ class ForgeSQLSelectOperations {
|
|
|
399
387
|
constructor(options) {
|
|
400
388
|
this.options = options;
|
|
401
389
|
}
|
|
402
|
-
/**
|
|
403
|
-
* Executes a Drizzle query and returns the results.
|
|
404
|
-
* Maps the raw database results to the appropriate entity types.
|
|
405
|
-
*
|
|
406
|
-
* @template T - The type of the query builder
|
|
407
|
-
* @param {T} query - The Drizzle query to execute
|
|
408
|
-
* @returns {Promise<Awaited<T>>} The query results mapped to entity types
|
|
409
|
-
*/
|
|
410
|
-
async executeQuery(query) {
|
|
411
|
-
const queryType = query;
|
|
412
|
-
const querySql = queryType.toSQL();
|
|
413
|
-
const datas = await this.executeRawSQL(querySql.sql, querySql.params);
|
|
414
|
-
if (!datas.length) return [];
|
|
415
|
-
return datas.map((r) => {
|
|
416
|
-
const rawModel = r;
|
|
417
|
-
const newModel = {};
|
|
418
|
-
const columns = queryType.config.fields;
|
|
419
|
-
Object.entries(columns).forEach(([name, column]) => {
|
|
420
|
-
const { realColumn, aliasName } = this.extractColumnInfo(column);
|
|
421
|
-
const value = rawModel[aliasName];
|
|
422
|
-
if (value === null || value === void 0) {
|
|
423
|
-
newModel[name] = value;
|
|
424
|
-
return;
|
|
425
|
-
}
|
|
426
|
-
newModel[name] = this.parseColumnValue(realColumn, value);
|
|
427
|
-
});
|
|
428
|
-
return newModel;
|
|
429
|
-
});
|
|
430
|
-
}
|
|
431
|
-
/**
|
|
432
|
-
* Extracts column information and alias name from a column definition.
|
|
433
|
-
* @param {any} column - The column definition from Drizzle
|
|
434
|
-
* @returns {Object} Object containing the real column and its alias name
|
|
435
|
-
*/
|
|
436
|
-
extractColumnInfo(column) {
|
|
437
|
-
if (column instanceof drizzleOrm.SQL) {
|
|
438
|
-
const realColumnSql = column;
|
|
439
|
-
const realColumn = realColumnSql.queryChunks.find(
|
|
440
|
-
(q) => q instanceof drizzleOrm.Column
|
|
441
|
-
);
|
|
442
|
-
let stringChunk = this.findAliasChunk(realColumnSql);
|
|
443
|
-
let withoutAlias = false;
|
|
444
|
-
if (!realColumn && (!stringChunk || !stringChunk.value || !stringChunk.value?.length)) {
|
|
445
|
-
stringChunk = realColumnSql.queryChunks.filter((q) => q instanceof drizzleOrm.StringChunk).find((q) => q.value[0]);
|
|
446
|
-
withoutAlias = true;
|
|
447
|
-
}
|
|
448
|
-
const aliasName = this.resolveAliasName(stringChunk, realColumn, withoutAlias);
|
|
449
|
-
return { realColumn, aliasName };
|
|
450
|
-
}
|
|
451
|
-
return { realColumn: column, aliasName: column.name };
|
|
452
|
-
}
|
|
453
|
-
/**
|
|
454
|
-
* Finds the alias chunk in SQL query chunks.
|
|
455
|
-
* @param {SQL} realColumnSql - The SQL query chunks
|
|
456
|
-
* @returns {StringChunk | undefined} The string chunk containing the alias or undefined
|
|
457
|
-
*/
|
|
458
|
-
findAliasChunk(realColumnSql) {
|
|
459
|
-
return realColumnSql.queryChunks.filter((q) => q instanceof drizzleOrm.StringChunk).find(
|
|
460
|
-
(q) => q.value.find((f) => f.toLowerCase().includes("as"))
|
|
461
|
-
);
|
|
462
|
-
}
|
|
463
|
-
/**
|
|
464
|
-
* Resolves the alias name from the string chunk or column.
|
|
465
|
-
* @param {StringChunk | undefined} stringChunk - The string chunk containing the alias
|
|
466
|
-
* @param {Column | undefined} realColumn - The real column definition
|
|
467
|
-
* @param {boolean} withoutAlias - Whether the column has no alias
|
|
468
|
-
* @returns {string} The resolved alias name
|
|
469
|
-
*/
|
|
470
|
-
resolveAliasName(stringChunk, realColumn, withoutAlias) {
|
|
471
|
-
if (stringChunk) {
|
|
472
|
-
if (withoutAlias) {
|
|
473
|
-
return stringChunk.value[0];
|
|
474
|
-
}
|
|
475
|
-
const asClause = stringChunk.value.find((f) => f.toLowerCase().includes("as"));
|
|
476
|
-
return asClause ? extractAlias(asClause.trim()) : realColumn?.name || "";
|
|
477
|
-
}
|
|
478
|
-
return realColumn?.name || "";
|
|
479
|
-
}
|
|
480
|
-
/**
|
|
481
|
-
* Parses a column value based on its SQL type.
|
|
482
|
-
* Handles datetime, date, and time types with appropriate formatting.
|
|
483
|
-
*
|
|
484
|
-
* @param {Column} column - The column definition
|
|
485
|
-
* @param {unknown} value - The raw value to parse
|
|
486
|
-
* @returns {unknown} The parsed value
|
|
487
|
-
*/
|
|
488
|
-
parseColumnValue(column, value) {
|
|
489
|
-
if (!column) return value;
|
|
490
|
-
switch (column.getSQLType()) {
|
|
491
|
-
case "datetime":
|
|
492
|
-
return parseDateTime(value, "YYYY-MM-DDTHH:mm:ss.SSS");
|
|
493
|
-
case "date":
|
|
494
|
-
return parseDateTime(value, "YYYY-MM-DD");
|
|
495
|
-
case "time":
|
|
496
|
-
return parseDateTime(value, "HH:mm:ss.SSS");
|
|
497
|
-
default:
|
|
498
|
-
return value;
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
390
|
/**
|
|
502
391
|
* Executes a Drizzle query and returns a single result.
|
|
503
392
|
* Throws an error if more than one record is returned.
|
|
@@ -508,7 +397,7 @@ class ForgeSQLSelectOperations {
|
|
|
508
397
|
* @throws {Error} If more than one record is returned
|
|
509
398
|
*/
|
|
510
399
|
async executeQueryOnlyOne(query) {
|
|
511
|
-
const results = await
|
|
400
|
+
const results = await query;
|
|
512
401
|
const datas = results;
|
|
513
402
|
if (!datas.length) {
|
|
514
403
|
return void 0;
|
|
@@ -533,6 +422,9 @@ class ForgeSQLSelectOperations {
|
|
|
533
422
|
}
|
|
534
423
|
const sqlStatement = sql.sql.prepare(query);
|
|
535
424
|
if (params) {
|
|
425
|
+
if (this.options.logRawSqlQuery && this.options.logRawSqlQueryParams) {
|
|
426
|
+
console.debug("Executing with SQL Params: " + JSON.stringify(params));
|
|
427
|
+
}
|
|
536
428
|
sqlStatement.bindParams(...params);
|
|
537
429
|
}
|
|
538
430
|
const result = await sqlStatement.execute();
|
|
@@ -553,6 +445,33 @@ class ForgeSQLSelectOperations {
|
|
|
553
445
|
return updateQueryResponseResults.rows;
|
|
554
446
|
}
|
|
555
447
|
}
|
|
448
|
+
const forgeDriver = {
|
|
449
|
+
query: async (query, params) => {
|
|
450
|
+
try {
|
|
451
|
+
const sqlStatement = await sql.sql.prepare(query.sql);
|
|
452
|
+
if (params) {
|
|
453
|
+
await sqlStatement.bindParams(...params);
|
|
454
|
+
}
|
|
455
|
+
const result = await sqlStatement.execute();
|
|
456
|
+
let rows;
|
|
457
|
+
if (Array.isArray(result.rows)) {
|
|
458
|
+
rows = [
|
|
459
|
+
result.rows.map((r) => Object.values(r))
|
|
460
|
+
];
|
|
461
|
+
} else {
|
|
462
|
+
rows = [
|
|
463
|
+
result.rows
|
|
464
|
+
];
|
|
465
|
+
}
|
|
466
|
+
return rows;
|
|
467
|
+
} catch (error) {
|
|
468
|
+
console.error("SQL Error:", JSON.stringify(error));
|
|
469
|
+
throw error;
|
|
470
|
+
}
|
|
471
|
+
},
|
|
472
|
+
transaction: async (transactionFn) => {
|
|
473
|
+
}
|
|
474
|
+
};
|
|
556
475
|
class ForgeSQLORMImpl {
|
|
557
476
|
static instance = null;
|
|
558
477
|
drizzle;
|
|
@@ -571,7 +490,7 @@ class ForgeSQLORMImpl {
|
|
|
571
490
|
if (newOptions.logRawSqlQuery) {
|
|
572
491
|
console.debug("Initializing ForgeSQLORM...");
|
|
573
492
|
}
|
|
574
|
-
this.drizzle = mysql2.drizzle(
|
|
493
|
+
this.drizzle = mysql2.drizzle(forgeDriver);
|
|
575
494
|
this.crudOperations = new ForgeSQLCrudOperations(this, newOptions);
|
|
576
495
|
this.fetchOperations = new ForgeSQLSelectOperations(newOptions);
|
|
577
496
|
} catch (error) {
|
|
@@ -700,6 +619,7 @@ exports.ForgeSQLCrudOperations = ForgeSQLCrudOperations;
|
|
|
700
619
|
exports.ForgeSQLSelectOperations = ForgeSQLSelectOperations;
|
|
701
620
|
exports.default = ForgeSQLORM;
|
|
702
621
|
exports.extractAlias = extractAlias;
|
|
622
|
+
exports.forgeDriver = forgeDriver;
|
|
703
623
|
exports.getPrimaryKeys = getPrimaryKeys;
|
|
704
624
|
exports.getTableMetadata = getTableMetadata;
|
|
705
625
|
exports.mySqlDateString = mySqlDateString;
|