forge-sql-orm 1.0.31 → 2.0.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 +216 -695
- package/dist/ForgeSQLORM.js +526 -564
- package/dist/ForgeSQLORM.js.map +1 -1
- package/dist/ForgeSQLORM.mjs +527 -554
- package/dist/ForgeSQLORM.mjs.map +1 -1
- package/dist/core/ForgeSQLCrudOperations.d.ts +101 -130
- package/dist/core/ForgeSQLCrudOperations.d.ts.map +1 -1
- package/dist/core/ForgeSQLORM.d.ts +11 -10
- package/dist/core/ForgeSQLORM.d.ts.map +1 -1
- package/dist/core/ForgeSQLQueryBuilder.d.ts +275 -111
- package/dist/core/ForgeSQLQueryBuilder.d.ts.map +1 -1
- package/dist/core/ForgeSQLSelectOperations.d.ts +65 -22
- package/dist/core/ForgeSQLSelectOperations.d.ts.map +1 -1
- package/dist/core/SystemTables.d.ts +59 -0
- package/dist/core/SystemTables.d.ts.map +1 -0
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/utils/sqlUtils.d.ts +53 -6
- package/dist/utils/sqlUtils.d.ts.map +1 -1
- package/dist-cli/cli.js +461 -397
- package/dist-cli/cli.js.map +1 -1
- package/dist-cli/cli.mjs +461 -397
- package/dist-cli/cli.mjs.map +1 -1
- package/package.json +21 -27
- package/src/core/ForgeSQLCrudOperations.ts +360 -473
- package/src/core/ForgeSQLORM.ts +38 -79
- package/src/core/ForgeSQLQueryBuilder.ts +255 -132
- package/src/core/ForgeSQLSelectOperations.ts +185 -72
- package/src/core/SystemTables.ts +7 -0
- package/src/index.ts +1 -2
- package/src/utils/sqlUtils.ts +164 -22
- package/dist/core/ComplexQuerySchemaBuilder.d.ts +0 -38
- package/dist/core/ComplexQuerySchemaBuilder.d.ts.map +0 -1
- package/dist/knex/index.d.ts +0 -4
- package/dist/knex/index.d.ts.map +0 -1
- package/src/core/ComplexQuerySchemaBuilder.ts +0 -63
- package/src/knex/index.ts +0 -4
package/README.md
CHANGED
|
@@ -2,16 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://github.com/vzakharchenko/forge-sql-orm/actions/workflows/node.js.yml)
|
|
4
4
|
|
|
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 [
|
|
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
|
-
- ✅ **Supports complex SQL queries** with joins and filtering
|
|
9
|
-
- ✅ **Batch insert support** with duplicate key handling
|
|
10
|
-
- ✅ **Schema migration support**, allowing automatic schema evolution
|
|
11
|
-
- ✅ **Automatic entity generation** from MySQL/tidb databases
|
|
12
|
-
- ✅ **Automatic migration generation** from MySQL/tidb databases
|
|
8
|
+
- ✅ **Supports complex SQL queries** with joins and filtering using Drizzle ORM
|
|
9
|
+
- ✅ **Batch insert support** with duplicate key handling
|
|
10
|
+
- ✅ **Schema migration support**, allowing automatic schema evolution
|
|
11
|
+
- ✅ **Automatic entity generation** from MySQL/tidb databases
|
|
12
|
+
- ✅ **Automatic migration generation** from MySQL/tidb databases
|
|
13
13
|
- ✅ **Drop Migrations** Generate a migration to drop all tables and clear migrations history for subsequent schema recreation
|
|
14
|
-
- ✅ **Optimistic Locking** Ensures data consistency by preventing conflicts when multiple users update the same record
|
|
14
|
+
- ✅ **Optimistic Locking** Ensures data consistency by preventing conflicts when multiple users update the same record
|
|
15
|
+
- ✅ **Type Safety** Full TypeScript support with proper type inference
|
|
15
16
|
|
|
16
17
|
## Installation
|
|
17
18
|
|
|
@@ -22,51 +23,22 @@ Forge-SQL-ORM is designed to work with @forge/sql and requires some additional s
|
|
|
22
23
|
```sh
|
|
23
24
|
npm install forge-sql-orm -S
|
|
24
25
|
npm install @forge/sql -S
|
|
25
|
-
npm
|
|
26
|
-
npm
|
|
26
|
+
npm install drizzle-orm mysql2
|
|
27
|
+
npm install mysql2 @types/mysql2 -D
|
|
27
28
|
```
|
|
28
29
|
|
|
29
30
|
This will:
|
|
30
|
-
|
|
31
|
-
Install
|
|
32
|
-
Install
|
|
33
|
-
|
|
34
|
-
✅ Step 2: Configure Post-Installation Patch
|
|
35
|
-
By default, MikroORM and Knex include some features that are not compatible with Forge's restricted runtime.
|
|
36
|
-
To fix this, we need to patch these libraries after installation.
|
|
37
|
-
|
|
38
|
-
Run:
|
|
39
|
-
|
|
40
|
-
```sh
|
|
41
|
-
npm pkg set scripts.postinstall="forge-sql-orm patch:mikroorm"
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
✅ Step 3: Apply the Patch
|
|
45
|
-
After setting up the postinstall script, run:
|
|
46
|
-
|
|
47
|
-
```sh
|
|
48
|
-
npm i
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
This will:
|
|
52
|
-
|
|
53
|
-
Trigger the postinstall hook, which applies the necessary patches to MikroORM and Knex.
|
|
54
|
-
Ensure everything is correctly configured for running inside Forge.
|
|
55
|
-
|
|
56
|
-
🔧 Why is the Patch Required?
|
|
57
|
-
Atlassian Forge has a restricted execution environment, which does not allow:
|
|
58
|
-
|
|
59
|
-
- Dynamic import(id) calls, commonly used in MikroORM.
|
|
60
|
-
- Direct file system access, which MikroORM sometimes relies on.
|
|
61
|
-
- Unsupported database dialects, such as PostgreSQL or SQLite.
|
|
62
|
-
- The patch removes these unsupported features to ensure full compatibility.
|
|
31
|
+
- Install Forge-SQL-ORM (the ORM for @forge/sql)
|
|
32
|
+
- Install @forge/sql, the Forge database layer
|
|
33
|
+
- Install Drizzle ORM and its MySQL driver
|
|
34
|
+
- Install TypeScript types for MySQL
|
|
63
35
|
|
|
64
36
|
## Step-by-Step Migration Workflow
|
|
65
37
|
|
|
66
|
-
1. **Generate initial
|
|
38
|
+
1. **Generate initial schema from an existing database**
|
|
67
39
|
|
|
68
40
|
```sh
|
|
69
|
-
npx forge-sql-orm generate:model --dbName testDb --output ./database/
|
|
41
|
+
npx forge-sql-orm generate:model --dbName testDb --output ./database/schema
|
|
70
42
|
```
|
|
71
43
|
|
|
72
44
|
_(This is done only once when setting up the project)_
|
|
@@ -74,7 +46,7 @@ Atlassian Forge has a restricted execution environment, which does not allow:
|
|
|
74
46
|
2. **Create the first migration**
|
|
75
47
|
|
|
76
48
|
```sh
|
|
77
|
-
npx forge-sql-orm migrations:create --dbName testDb --entitiesPath ./database/
|
|
49
|
+
npx forge-sql-orm migrations:create --dbName testDb --entitiesPath ./database/schema --output ./database/migration
|
|
78
50
|
```
|
|
79
51
|
|
|
80
52
|
_(This initializes the database migration structure, also done once)_
|
|
@@ -91,387 +63,28 @@ Atlassian Forge has a restricted execution environment, which does not allow:
|
|
|
91
63
|
5. **Update the migration**
|
|
92
64
|
|
|
93
65
|
```sh
|
|
94
|
-
npx forge-sql-orm migrations:update --dbName testDb --entitiesPath ./database/
|
|
66
|
+
npx forge-sql-orm migrations:update --dbName testDb --entitiesPath ./database/schema --output ./database/migration
|
|
95
67
|
```
|
|
96
68
|
|
|
97
|
-
- ⚠️ **Do NOT update
|
|
98
|
-
- If
|
|
69
|
+
- ⚠️ **Do NOT update schema before this step!**
|
|
70
|
+
- If schema is updated first, the migration will be empty!
|
|
99
71
|
|
|
100
72
|
6. **Deploy to Forge and verify that the migration runs without issues**
|
|
101
73
|
|
|
102
74
|
- Run the updated migration on Forge.
|
|
103
75
|
|
|
104
|
-
7. **Update the
|
|
76
|
+
7. **Update the schema**
|
|
105
77
|
|
|
106
78
|
```sh
|
|
107
|
-
npx forge-sql-orm generate:model --dbName testDb --output ./database/
|
|
79
|
+
npx forge-sql-orm generate:model --dbName testDb --output ./database/schema
|
|
108
80
|
```
|
|
109
81
|
|
|
110
82
|
8. **Repeat steps 4-7 as needed**
|
|
111
83
|
|
|
112
84
|
**⚠️ WARNING:**
|
|
113
85
|
|
|
114
|
-
- **Do NOT swap steps 7 and 5!** If you update
|
|
115
|
-
- Always generate the **migration first**, then update the **
|
|
116
|
-
|
|
117
|
-
---
|
|
118
|
-
|
|
119
|
-
# Connection to ORM
|
|
120
|
-
|
|
121
|
-
```js
|
|
122
|
-
import ForgeSQL from "forge-sql-orm";
|
|
123
|
-
import { Orders } from "./entities/Orders";
|
|
124
|
-
import { Users } from "./entities/Users";
|
|
125
|
-
import ENTITIES from "./entities";
|
|
126
|
-
|
|
127
|
-
const forgeSQL = new ForgeSQL(ENTITIES);
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
- Fetch Data:
|
|
131
|
-
|
|
132
|
-
```js
|
|
133
|
-
const formattedQuery = forgeSQL
|
|
134
|
-
.createQueryBuilder(Users)
|
|
135
|
-
.select("*")
|
|
136
|
-
.limit(limit)
|
|
137
|
-
.offset(offset)
|
|
138
|
-
.getFormattedQuery();
|
|
139
|
-
//select `u0`.* from `users` as `u0` limit 10 offset 1
|
|
140
|
-
return await forgeSQL.fetch().executeSchemaSQL(formattedQuery, UsersSchema);
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
- Raw Fetch Data
|
|
144
|
-
|
|
145
|
-
```js
|
|
146
|
-
const users = (await forgeSQL.fetch().executeRawSQL) < Users > "SELECT * FROM users";
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
- Complex Query
|
|
150
|
-
|
|
151
|
-
```js
|
|
152
|
-
// Define schema for join result
|
|
153
|
-
const innerJoinSchema = forgeSQL.fetch().createComplexQuerySchema();
|
|
154
|
-
schemaBuilder.addField(Users.meta.properties.name);
|
|
155
|
-
schemaBuilder.addField(Orders.meta.properties.product);
|
|
156
|
-
|
|
157
|
-
// Execute query
|
|
158
|
-
const query = forgeSQL.createQueryBuilder(Orders, "order")
|
|
159
|
-
.limit(10).offset(10)
|
|
160
|
-
.innerJoin("user", "user")
|
|
161
|
-
.select(["user.name", "order.product"])
|
|
162
|
-
.getFormattedQuery();
|
|
163
|
-
// select `user`.`name`, `order`.`product` from `orders` as `order` inner join `users` as `user` on `order`.`user_id` = `user`.`id` limit 10 offset 10
|
|
164
|
-
const results = await forgeSQL.fetch().executeSchemaSQL(query, innerJoinSchema);
|
|
165
|
-
console.log(results);
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
## CRUD Operations
|
|
169
|
-
|
|
170
|
-
- **Insert Data**
|
|
171
|
-
|
|
172
|
-
```js
|
|
173
|
-
// INSERT INTO users (id, name) VALUES (1, 'Smith')
|
|
174
|
-
const userId = await forgeSQL.crud().insert(UsersSchema, [{ id: 1, name: "Smith" }]);
|
|
175
|
-
```
|
|
176
|
-
|
|
177
|
-
- **Insert Bulk Data**
|
|
178
|
-
|
|
179
|
-
```js
|
|
180
|
-
// INSERT INTO users (id, name) VALUES (2, 'Smith'), (3, 'Vasyl')
|
|
181
|
-
await forgeSQL.crud().insert(UsersSchema, [
|
|
182
|
-
{ id: 2, name: "Smith" },
|
|
183
|
-
{ id: 3, name: "Vasyl" },
|
|
184
|
-
]);
|
|
185
|
-
```
|
|
186
|
-
|
|
187
|
-
- **Insert Data with Duplicates**
|
|
188
|
-
|
|
189
|
-
```js
|
|
190
|
-
// INSERT INTO users (id, name) VALUES (4, 'Smith'), (4, 'Vasyl')
|
|
191
|
-
// ON DUPLICATE KEY UPDATE name = VALUES(name)
|
|
192
|
-
await forgeSQL.crud().insert(
|
|
193
|
-
UsersSchema,
|
|
194
|
-
[
|
|
195
|
-
{ id: 4, name: "Smith" },
|
|
196
|
-
{ id: 4, name: "Vasyl" },
|
|
197
|
-
],
|
|
198
|
-
true,
|
|
199
|
-
);
|
|
200
|
-
```
|
|
201
|
-
|
|
202
|
-
- **Update Data by Primary Key**
|
|
203
|
-
|
|
204
|
-
```js
|
|
205
|
-
// This uses the updateById method which wraps updateFieldById (with optimistic locking if configured)
|
|
206
|
-
await forgeSQL.crud().updateById({ id: 1, name: "Smith Updated" }, UsersSchema);
|
|
207
|
-
```
|
|
208
|
-
|
|
209
|
-
- **Update Specific Fields by Primary Key**
|
|
210
|
-
|
|
211
|
-
```js
|
|
212
|
-
// Updates specific fields of a record identified by its primary key.
|
|
213
|
-
// Note: The primary key field (e.g. id) must be included in the fields array.
|
|
214
|
-
await forgeSQL.crud().updateFieldById({ id: 1, name: "Updated Name" }, ["id", "name"], UsersSchema);
|
|
215
|
-
```
|
|
216
|
-
|
|
217
|
-
- **Update Fields Without Primary Key and Versioning**
|
|
218
|
-
|
|
219
|
-
```js
|
|
220
|
-
// Updates specified fields for records matching the given conditions.
|
|
221
|
-
// In this example, the "name" and "age" fields are updated for users where the email is 'smith@example.com'.
|
|
222
|
-
const affectedRows = await forgeSQL.crud().updateFields(
|
|
223
|
-
{ name: "New Name", age: 35, email: "smith@example.com" },
|
|
224
|
-
["name", "age"],
|
|
225
|
-
UsersSchema
|
|
226
|
-
);
|
|
227
|
-
console.log(`Rows affected: ${affectedRows}`);
|
|
228
|
-
|
|
229
|
-
// Alternatively, you can provide an explicit WHERE condition:
|
|
230
|
-
const affectedRowsWithWhere = await forgeSQL.crud().updateFields(
|
|
231
|
-
{ name: "New Name", age: 35 },
|
|
232
|
-
["name", "age"],
|
|
233
|
-
UsersSchema,
|
|
234
|
-
{ email: "smith@example.com" }
|
|
235
|
-
);
|
|
236
|
-
console.log(`Rows affected: ${affectedRowsWithWhere}`);
|
|
237
|
-
```
|
|
238
|
-
|
|
239
|
-
- **Delete Data**
|
|
240
|
-
|
|
241
|
-
```js
|
|
242
|
-
await forgeSQL.crud().deleteById(1, UsersSchema);
|
|
243
|
-
```
|
|
244
|
-
|
|
245
|
-
## Quick Start
|
|
246
|
-
|
|
247
|
-
### 1. Designing the Database
|
|
248
|
-
|
|
249
|
-
You can start by designing a **MySQL/tidb database** using tools like [DbSchema](https://dbschema.com/) or by using an existing MySQL/tidb database.
|
|
250
|
-
|
|
251
|
-
**Schema visualization:**
|
|
252
|
-

|
|
253
|
-
|
|
254
|
-
#### DDL Scripts
|
|
255
|
-
|
|
256
|
-
```sql
|
|
257
|
-
CREATE DATABASE testDb;
|
|
258
|
-
CREATE TABLE testDb.users (
|
|
259
|
-
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
|
260
|
-
name VARCHAR(200)
|
|
261
|
-
) engine=InnoDB;
|
|
262
|
-
|
|
263
|
-
CREATE TABLE testDb.orders (
|
|
264
|
-
id INT NOT NULL PRIMARY KEY,
|
|
265
|
-
user_id INT NOT NULL ,
|
|
266
|
-
product VARCHAR(200)
|
|
267
|
-
) engine=InnoDB;
|
|
268
|
-
|
|
269
|
-
ALTER TABLE testDb.orders ADD CONSTRAINT fk_orders_users FOREIGN KEY ( user_id ) REFERENCES testDb.users( id ) ON DELETE NO ACTION ON UPDATE NO ACTION;
|
|
270
|
-
```
|
|
271
|
-
|
|
272
|
-
### 2. Generate Models
|
|
273
|
-
|
|
274
|
-
Run the following command to generate entity models based on your database:
|
|
275
|
-
|
|
276
|
-
```sh
|
|
277
|
-
npx forge-sql-orm generate:model --host localhost --port 3306 --user root --password secret --dbName testDb --output ./database/entities
|
|
278
|
-
```
|
|
279
|
-
|
|
280
|
-
This will generate **entity schemas** in the `./database/entities` directory.
|
|
281
|
-
Users Model and Schema:
|
|
282
|
-
|
|
283
|
-
```js
|
|
284
|
-
import { EntitySchema } from 'forge-sql-orm';
|
|
285
|
-
|
|
286
|
-
export class Users {
|
|
287
|
-
id!: number;
|
|
288
|
-
name?: string;
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
export const UsersSchema = new EntitySchema({
|
|
292
|
-
class: Users,
|
|
293
|
-
properties: {
|
|
294
|
-
id: { primary: true, type: 'integer', unsigned: false },
|
|
295
|
-
name: { type: 'string', length: 200, nullable: true },
|
|
296
|
-
},
|
|
297
|
-
});
|
|
298
|
-
|
|
299
|
-
```
|
|
300
|
-
|
|
301
|
-
Orders Model and Schema:
|
|
302
|
-
|
|
303
|
-
```js
|
|
304
|
-
import { EntitySchema } from 'forge-sql-orm';
|
|
305
|
-
import { Users } from './Users';
|
|
306
|
-
|
|
307
|
-
export class Orders {
|
|
308
|
-
id!: number;
|
|
309
|
-
user!: Users;
|
|
310
|
-
userId!: number;
|
|
311
|
-
product?: string;
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
export const OrdersSchema = new EntitySchema({
|
|
315
|
-
class: Orders,
|
|
316
|
-
properties: {
|
|
317
|
-
id: { primary: true, type: 'integer', unsigned: false, autoincrement: false },
|
|
318
|
-
user: {
|
|
319
|
-
kind: 'm:1',
|
|
320
|
-
entity: () => Users,
|
|
321
|
-
fieldName: 'user_id',
|
|
322
|
-
index: 'fk_orders_users',
|
|
323
|
-
},
|
|
324
|
-
userId: {
|
|
325
|
-
type: 'integer',
|
|
326
|
-
fieldName: 'user_id',
|
|
327
|
-
persist: false,
|
|
328
|
-
index: 'fk_orders_users',
|
|
329
|
-
},
|
|
330
|
-
product: { type: 'string', length: 200, nullable: true },
|
|
331
|
-
},
|
|
332
|
-
});
|
|
333
|
-
```
|
|
334
|
-
|
|
335
|
-
index.ts
|
|
336
|
-
|
|
337
|
-
```js
|
|
338
|
-
import { Orders } from "./Orders";
|
|
339
|
-
import { Users } from "./Users";
|
|
340
|
-
|
|
341
|
-
export default [Orders, Users];
|
|
342
|
-
```
|
|
343
|
-
|
|
344
|
-
### 3. Create the First Migration
|
|
345
|
-
|
|
346
|
-
After generating the models, create the first migration file that represents the current database state:
|
|
347
|
-
|
|
348
|
-
```sh
|
|
349
|
-
npx forge-sql-orm migrations:create --dbName testDb --entitiesPath ./database/entities --output ./database/migration
|
|
350
|
-
```
|
|
351
|
-
|
|
352
|
-
Generated migration:
|
|
353
|
-
|
|
354
|
-
```js
|
|
355
|
-
import { MigrationRunner } from "@forge/sql/out/migration";
|
|
356
|
-
|
|
357
|
-
export default (migrationRunner: MigrationRunner): MigrationRunner => {
|
|
358
|
-
return migrationRunner
|
|
359
|
-
.enqueue("v1_MIGRATION0", "create table `users` (`id` int not null auto_increment primary key, `name` varchar(200) null)")
|
|
360
|
-
.enqueue("v1_MIGRATION1", "create table `orders` (`id` int not null, `user_id` int not null, `product` varchar(200) null, primary key (`id`))")
|
|
361
|
-
.enqueue("v1_MIGRATION2", "alter table `orders` add index `fk_orders_users`(`user_id`)");
|
|
362
|
-
};
|
|
363
|
-
```
|
|
364
|
-
|
|
365
|
-
### 4. Deploy and Run Migrations
|
|
366
|
-
|
|
367
|
-
```js
|
|
368
|
-
import migration from "./database/migration";
|
|
369
|
-
import sql, { migrationRunner } from "@forge/sql";
|
|
370
|
-
|
|
371
|
-
export const trigger = async () => {
|
|
372
|
-
console.log("Provisioning the database");
|
|
373
|
-
await sql._provision();
|
|
374
|
-
|
|
375
|
-
console.log("Running schema migrations");
|
|
376
|
-
const migrations = await migration(migrationRunner);
|
|
377
|
-
const successfulMigrations = await migrations.run();
|
|
378
|
-
console.log("Migrations applied:", successfulMigrations);
|
|
379
|
-
|
|
380
|
-
const migrationHistory = (await migrationRunner.list())
|
|
381
|
-
.map((y) => `${y.id}, ${y.name}, ${y.migratedAt.toUTCString()}`)
|
|
382
|
-
.join("\n");
|
|
383
|
-
|
|
384
|
-
console.log("Migrations history:\nid, name, migrated_at\n", migrationHistory);
|
|
385
|
-
|
|
386
|
-
return {
|
|
387
|
-
headers: { "Content-Type": ["application/json"] },
|
|
388
|
-
statusCode: 200,
|
|
389
|
-
statusText: "OK",
|
|
390
|
-
body: "Migrations successfully executed",
|
|
391
|
-
};
|
|
392
|
-
};
|
|
393
|
-
```
|
|
394
|
-
|
|
395
|
-
### 5. Running Queries
|
|
396
|
-
|
|
397
|
-
Once the migrations are applied, you can start working with the ORM:
|
|
398
|
-
|
|
399
|
-
```js
|
|
400
|
-
import Entities from "./entities";
|
|
401
|
-
import ForgeSQL from "forge-sql-orm";
|
|
402
|
-
import { UsersSchema, Users } from "./entities/Users";
|
|
403
|
-
|
|
404
|
-
const forgeSQL = new ForgeSQL(ENTITIES);
|
|
405
|
-
|
|
406
|
-
// Insert Data
|
|
407
|
-
const user = new Users();
|
|
408
|
-
user.name = "John Doe";
|
|
409
|
-
const userId = await forgeSQL.crud().insert(UsersSchema, [user]);
|
|
410
|
-
console.log("Inserted User ID:", userId);
|
|
411
|
-
|
|
412
|
-
// Fetch Users
|
|
413
|
-
const users = await forgeSQL.fetch().executeSchemaSQL("SELECT * FROM users", UsersSchema);
|
|
414
|
-
console.log(users);
|
|
415
|
-
```
|
|
416
|
-
|
|
417
|
-
### 6. Updating Database Schema
|
|
418
|
-
|
|
419
|
-
If you modify the database schema, you can generate an update migration:
|
|
420
|
-
|
|
421
|
-
Modify the schema in DbSchema
|
|
422
|
-

|
|
423
|
-
or manually run:
|
|
424
|
-
|
|
425
|
-
```sh
|
|
426
|
-
ALTER TABLE `users` ADD email VARCHAR(255);
|
|
427
|
-
```
|
|
428
|
-
|
|
429
|
-
Then, generate a new migration:
|
|
430
|
-
|
|
431
|
-
```sh
|
|
432
|
-
npx forge-sql-orm migrations:update --dbName testDb --entitiesPath ./database/entities --output ./database/migration
|
|
433
|
-
```
|
|
434
|
-
|
|
435
|
-
Generated migration:
|
|
436
|
-
|
|
437
|
-
```js
|
|
438
|
-
import { MigrationRunner } from "@forge/sql/out/migration";
|
|
439
|
-
|
|
440
|
-
export default (migrationRunner: MigrationRunner): MigrationRunner => {
|
|
441
|
-
return migrationRunner.enqueue("v2_MIGRATION0", "alter table `users` add `email` varchar(255) null");
|
|
442
|
-
};
|
|
443
|
-
```
|
|
444
|
-
|
|
445
|
-
### 7. Updating Entities
|
|
446
|
-
|
|
447
|
-
After applying the migration, update your entity models:
|
|
448
|
-
|
|
449
|
-
```sh
|
|
450
|
-
npx forge-sql-orm generate:model --dbName testDb --output ./database/entities
|
|
451
|
-
```
|
|
452
|
-
|
|
453
|
-
Updated Users Model and Schema:
|
|
454
|
-
|
|
455
|
-
```js
|
|
456
|
-
import { EntitySchema } from 'forge-sql-orm';
|
|
457
|
-
|
|
458
|
-
export class Users {
|
|
459
|
-
id!: number;
|
|
460
|
-
name?: string;
|
|
461
|
-
email?: string;
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
export const UsersSchema = new EntitySchema({
|
|
465
|
-
class: Users,
|
|
466
|
-
properties: {
|
|
467
|
-
id: { primary: true, type: 'integer', unsigned: false },
|
|
468
|
-
name: { type: 'string', length: 200, nullable: true },
|
|
469
|
-
email: { type: 'string', nullable: true },
|
|
470
|
-
},
|
|
471
|
-
});
|
|
472
|
-
|
|
473
|
-
```
|
|
474
|
-
---
|
|
86
|
+
- **Do NOT swap steps 7 and 5!** If you update schema before generating a migration, the migration will be empty!
|
|
87
|
+
- Always generate the **migration first**, then update the **schema**.
|
|
475
88
|
|
|
476
89
|
## Drop Migrations
|
|
477
90
|
|
|
@@ -479,25 +92,25 @@ The Drop Migrations feature allows you to completely reset your database schema
|
|
|
479
92
|
- Start fresh with a new schema
|
|
480
93
|
- Reset all tables and their data
|
|
481
94
|
- Clear migration history
|
|
482
|
-
- Ensure your local
|
|
95
|
+
- Ensure your local schema matches the deployed database
|
|
483
96
|
|
|
484
97
|
### Important Requirements
|
|
485
98
|
|
|
486
99
|
Before using Drop Migrations, ensure that:
|
|
487
|
-
1. Your local
|
|
100
|
+
1. Your local schema exactly matches the current database schema deployed in Atlassian Forge SQL
|
|
488
101
|
2. You have a backup of your data if needed
|
|
489
102
|
3. You understand that this operation will delete all tables and data
|
|
490
103
|
|
|
491
104
|
### Usage
|
|
492
105
|
|
|
493
|
-
1. First, ensure your local
|
|
106
|
+
1. First, ensure your local schema matches the deployed database:
|
|
494
107
|
```bash
|
|
495
|
-
npx forge-sql-orm generate:model --output ./database/
|
|
108
|
+
npx forge-sql-orm generate:model --output ./database/schema
|
|
496
109
|
```
|
|
497
110
|
|
|
498
111
|
2. Generate the drop migration:
|
|
499
112
|
```bash
|
|
500
|
-
npx forge-sql-orm migrations:drop --entitiesPath ./database/
|
|
113
|
+
npx forge-sql-orm migrations:drop --entitiesPath ./database/schema --output ./database/migration
|
|
501
114
|
```
|
|
502
115
|
|
|
503
116
|
3. Deploy and run the migration in your Forge app:
|
|
@@ -512,7 +125,7 @@ Before using Drop Migrations, ensure that:
|
|
|
512
125
|
|
|
513
126
|
4. After dropping all tables, you can create a new migration to recreate the schema:
|
|
514
127
|
```bash
|
|
515
|
-
npx forge-sql-orm migrations:create --entitiesPath ./database/
|
|
128
|
+
npx forge-sql-orm migrations:create --entitiesPath ./database/schema --output ./database/migration --force
|
|
516
129
|
```
|
|
517
130
|
The `--force` parameter is required here because we're creating a new migration after dropping all tables.
|
|
518
131
|
|
|
@@ -524,8 +137,10 @@ import { MigrationRunner } from "@forge/sql/out/migration";
|
|
|
524
137
|
|
|
525
138
|
export default (migrationRunner: MigrationRunner): MigrationRunner => {
|
|
526
139
|
return migrationRunner
|
|
527
|
-
.enqueue("v1_MIGRATION0", "
|
|
528
|
-
.enqueue("v1_MIGRATION1", "DROP
|
|
140
|
+
.enqueue("v1_MIGRATION0", "ALTER TABLE `orders` DROP FOREIGN KEY `fk_orders_users`")
|
|
141
|
+
.enqueue("v1_MIGRATION1", "DROP INDEX `idx_orders_user_id` ON `orders`")
|
|
142
|
+
.enqueue("v1_MIGRATION2", "DROP TABLE IF EXISTS `orders`")
|
|
143
|
+
.enqueue("v1_MIGRATION3", "DROP TABLE IF EXISTS `users`")
|
|
529
144
|
.enqueue("MIGRATION_V1_1234567890", "DELETE FROM __migrations");
|
|
530
145
|
};
|
|
531
146
|
```
|
|
@@ -533,339 +148,245 @@ export default (migrationRunner: MigrationRunner): MigrationRunner => {
|
|
|
533
148
|
### ⚠️ Important Notes
|
|
534
149
|
|
|
535
150
|
- This operation is **irreversible** - all data will be lost
|
|
536
|
-
- Make sure your local
|
|
151
|
+
- Make sure your local schema is up-to-date with the deployed database
|
|
537
152
|
- Consider backing up your data before running drop migrations
|
|
538
153
|
- The migration will clear the `__migrations` table to allow for fresh migration history
|
|
154
|
+
- Drop operations are performed in the correct order: first foreign keys, then indexes, then tables
|
|
539
155
|
|
|
540
156
|
---
|
|
541
157
|
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
### INNER JOIN Example
|
|
545
|
-
|
|
546
|
-
Using **ForgeSQLORM**, you can perform complex queries with joins.
|
|
547
|
-
For example, fetching **users and their purchased products**:
|
|
548
|
-
|
|
549
|
-
```ts
|
|
550
|
-
import ForgeSQL, { EntitySchema } from "forge-sql-orm";
|
|
551
|
-
import { Orders } from "./entities/Orders";
|
|
552
|
-
import { Users } from "./entities/Users";
|
|
553
|
-
import ENTITIES from "./entities";
|
|
554
|
-
|
|
555
|
-
const forgeSQL = new ForgeSQL(ENTITIES);
|
|
556
|
-
|
|
557
|
-
// Define schema for join result
|
|
558
|
-
class InnerJoinResult {
|
|
559
|
-
name!: string;
|
|
560
|
-
product!: string;
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
export const innerJoinSchema = new EntitySchema<InnerJoinResult>({
|
|
564
|
-
class: InnerJoinResult,
|
|
565
|
-
properties: {
|
|
566
|
-
name: { type: "string", fieldName: "name" },
|
|
567
|
-
product: { type: "string", fieldName: "product" },
|
|
568
|
-
},
|
|
569
|
-
});
|
|
570
|
-
innerJoinSchema.init();
|
|
571
|
-
|
|
572
|
-
// Execute query
|
|
573
|
-
const query = forgeSQL
|
|
574
|
-
.createQueryBuilder(Orders, "order")
|
|
575
|
-
.limit(10)
|
|
576
|
-
.offset(0)
|
|
577
|
-
.innerJoin("user", "user")
|
|
578
|
-
.select(["user.name", "order.product"])
|
|
579
|
-
.getFormattedQuery();
|
|
580
|
-
|
|
581
|
-
const results = await forgeSQL.fetch().executeSchemaSQL(query, innerJoinSchema);
|
|
582
|
-
console.log(results);
|
|
583
|
-
```
|
|
584
|
-
|
|
585
|
-
---
|
|
586
|
-
|
|
587
|
-
## ForgeSqlOrmOptions
|
|
588
|
-
|
|
589
|
-
The `ForgeSqlOrmOptions` object allows customization of ORM behavior. Currently, it supports the following options:
|
|
590
|
-
|
|
591
|
-
| Option | Type | Description |
|
|
592
|
-
| -------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
593
|
-
| `logRawSqlQuery` | `boolean` | Enables logging of raw SQL queries in the Atlassian Forge Developer Console. Useful for debugging and monitoring. Defaults to `false`. |
|
|
594
|
-
| `disableOptimisticLocking` | `boolean` | Disables optimistic locking. When set to `true`, no additional condition (e.g., a version check) is added during record updates, which can improve performance. However, this may lead to conflicts when multiple transactions attempt to update the same record concurrently. |
|
|
595
|
-
|
|
596
|
-
---
|
|
597
|
-
|
|
598
|
-
### Example: Initializing `ForgeSQL` with Options
|
|
158
|
+
# Connection to ORM
|
|
599
159
|
|
|
600
|
-
```
|
|
160
|
+
```js
|
|
601
161
|
import ForgeSQL from "forge-sql-orm";
|
|
602
|
-
import { Orders } from "./entities/Orders";
|
|
603
|
-
import { Users } from "./entities/Users";
|
|
604
|
-
import ENTITIES from "./entities";
|
|
605
162
|
|
|
606
|
-
const
|
|
607
|
-
logRawSqlQuery: true, // Enable query logging for debugging purposes
|
|
608
|
-
};
|
|
609
|
-
|
|
610
|
-
const forgeSQL = new ForgeSQL(ENTITIES, options);
|
|
163
|
+
const forgeSQL = new ForgeSQL();
|
|
611
164
|
```
|
|
612
165
|
|
|
613
|
-
##
|
|
614
|
-
|
|
615
|
-
The `getKnex()` method allows direct interaction with Knex.js, enabling execution of raw SQL queries and complex query building.
|
|
166
|
+
## Fetch Data
|
|
616
167
|
|
|
617
|
-
###
|
|
618
|
-
|
|
619
|
-
```typescript
|
|
620
|
-
const fields: string[] = ["name", "email"];
|
|
621
|
-
|
|
622
|
-
// Define selected fields, including a count of duplicate occurrences
|
|
623
|
-
const selectFields: Array<string | Knex.Raw> = [
|
|
624
|
-
...fields,
|
|
625
|
-
forgeSQL.getKnex().raw("COUNT(*) as count"),
|
|
626
|
-
];
|
|
627
|
-
|
|
628
|
-
// Create a QueryBuilder with grouping and filtering for duplicates
|
|
629
|
-
let selectQueryBuilder = forgeSQL
|
|
630
|
-
.createQueryBuilder(UsersSchema)
|
|
631
|
-
.select(selectFields as unknown as string[])
|
|
632
|
-
.groupBy(fields)
|
|
633
|
-
.having("COUNT(*) > 1");
|
|
634
|
-
|
|
635
|
-
// Generate the final SQL query with ordering by count
|
|
636
|
-
const query = selectQueryBuilder.getKnexQuery().orderByRaw("count ASC").toSQL().sql;
|
|
637
|
-
|
|
638
|
-
/*
|
|
639
|
-
SQL Query:
|
|
640
|
-
SELECT `u0`.`name`, `u0`.`email`, COUNT(*) as count
|
|
641
|
-
FROM `users` AS `u0`
|
|
642
|
-
GROUP BY `u0`.`name`, `u0`.`email`
|
|
643
|
-
HAVING COUNT(*) > 1
|
|
644
|
-
ORDER BY count ASC;
|
|
645
|
-
*/
|
|
646
|
-
|
|
647
|
-
// Execute the SQL query and retrieve results
|
|
648
|
-
const duplicateResult = await forgeSQL
|
|
649
|
-
.fetch()
|
|
650
|
-
.executeSchemaSQL<DuplicateResult>(query, DuplicateSchema);
|
|
651
|
-
```
|
|
652
|
-
|
|
653
|
-
🔹 **What does this example do?**
|
|
654
|
-
|
|
655
|
-
1. Selects `name` and `email`, along with the count of duplicate occurrences (`COUNT(*) as count`).
|
|
656
|
-
2. Groups the data by `name` and `email` to identify duplicates.
|
|
657
|
-
3. Filters the results to include only groups with more than one record (`HAVING COUNT(*) > 1`).
|
|
658
|
-
4. Sorts the final results in ascending order by count (`ORDER BY count ASC`).
|
|
659
|
-
5. Executes the SQL query and returns the duplicate records.
|
|
660
|
-
|
|
661
|
-
---
|
|
662
|
-
|
|
663
|
-
## Optimistic Locking
|
|
664
|
-
|
|
665
|
-
Optimistic locking is a concurrency control mechanism that prevents data conflicts when multiple transactions attempt to update the same record concurrently. Instead of using locks, this technique relies on a version field in your entity models. Each time an update occurs, the current version is checked, and if it doesn't match the stored version, the update is rejected. This ensures data consistency and helps avoid accidental overwrites.
|
|
666
|
-
|
|
667
|
-
### How It Works
|
|
668
|
-
|
|
669
|
-
- **Version Field:**
|
|
670
|
-
A specific field in your entity schema is designated to track the version of the record. This field is marked with the flag `version: true`.
|
|
671
|
-
|
|
672
|
-
- **Supported Types:**
|
|
673
|
-
The version field must be of type `datetime`, `timestamp`, `integer`, or `decimal` and must be non-nullable. If the field's type does not meet these requirements, a warning message will be logged to the console during model generation.
|
|
674
|
-
|
|
675
|
-
- **Automatic Configuration:**
|
|
676
|
-
When generating models, you can specify a field (e.g., `updatedAt`) that automatically becomes the version field by using the `--versionField` flag. For example:
|
|
677
|
-
```sh
|
|
678
|
-
npx forge-sql-orm generate:model --versionField updatedAt
|
|
679
|
-
```
|
|
680
|
-
In this case, any model that includes a field named `updatedAt` meeting the required conditions will have it configured for optimistic locking.
|
|
681
|
-
|
|
682
|
-
### Example
|
|
683
|
-
|
|
684
|
-
Here's how you can define an entity with optimistic locking using MikroORM:
|
|
168
|
+
### Basic Fetch Operations
|
|
685
169
|
|
|
686
170
|
```js
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
id: { primary: true, type: "integer", unsigned: false, autoincrement: false },
|
|
697
|
-
name: { type: "string", nullable: true },
|
|
698
|
-
version: { type: "integer", nullable: false, version: true },
|
|
699
|
-
},
|
|
700
|
-
});
|
|
701
|
-
```
|
|
702
|
-
|
|
703
|
-
In this example, the `version` field is used to track changes. Every update will check the current version to ensure that no conflicting modifications occur.
|
|
704
|
-
|
|
705
|
-
---
|
|
171
|
+
// Using executeQuery for single result
|
|
172
|
+
const user = await forgeSQL
|
|
173
|
+
.fetch()
|
|
174
|
+
.executeQuery(
|
|
175
|
+
forgeSQL.getDrizzleQueryBuilder()
|
|
176
|
+
.select("*").from(Users)
|
|
177
|
+
.where(eq(Users.id, 1))
|
|
178
|
+
);
|
|
179
|
+
// Returns: { id: 1, name: "John Doe" }
|
|
706
180
|
|
|
707
|
-
|
|
181
|
+
// Using executeQueryOnlyOne for single result with error handling
|
|
182
|
+
const user = await forgeSQL
|
|
183
|
+
.fetch()
|
|
184
|
+
.executeQueryOnlyOne(
|
|
185
|
+
forgeSQL
|
|
186
|
+
.getDrizzleQueryBuilder()
|
|
187
|
+
.select("*").from(Users)
|
|
188
|
+
.where(eq(Users.id, 1))
|
|
189
|
+
);
|
|
190
|
+
// Returns: { id: 1, name: "John Doe" }
|
|
191
|
+
// Throws error if multiple records found
|
|
192
|
+
// Returns undefined if no records found
|
|
708
193
|
|
|
709
|
-
|
|
194
|
+
// Using executeQuery with aliases
|
|
195
|
+
const usersAlias = alias(Users, "u");
|
|
196
|
+
const result = await forgeSQL
|
|
197
|
+
.fetch()
|
|
198
|
+
.executeQuery(
|
|
199
|
+
forgeSQL
|
|
200
|
+
.getDrizzleQueryBuilder()
|
|
201
|
+
.select({
|
|
202
|
+
userId: rawSql`${usersAlias.id} as \`userId\``,
|
|
203
|
+
userName: rawSql`${usersAlias.name} as \`userName\``
|
|
204
|
+
}).from(usersAlias)
|
|
205
|
+
);
|
|
206
|
+
// Returns: { userId: 1, userName: "John Doe" }
|
|
710
207
|
|
|
711
|
-
|
|
712
|
-
|
|
208
|
+
// Using executeQuery with joins
|
|
209
|
+
const orderWithUser = await forgeSQL
|
|
210
|
+
.fetch()
|
|
211
|
+
.executeQuery(
|
|
212
|
+
forgeSQL
|
|
213
|
+
.getDrizzleQueryBuilder()
|
|
214
|
+
.select({
|
|
215
|
+
orderId: rawSql`${Orders.id} as \`orderId\``,
|
|
216
|
+
product: Orders.product,
|
|
217
|
+
userName: rawSql`${Users.name} as \`userName\``
|
|
218
|
+
}).from(Orders)
|
|
219
|
+
.innerJoin(Users, eq(Orders.userId, Users.id))
|
|
220
|
+
.where(eq(Orders.id, 1))
|
|
221
|
+
);
|
|
222
|
+
// Returns: { orderId: 1, product: "Product 1", userName: "John Doe" }
|
|
713
223
|
```
|
|
714
224
|
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
## Forge SQL ORM CLI Documentation
|
|
718
|
-
|
|
719
|
-
The CLI provides commands to generate models and manage migrations for MikroORM in Forge.
|
|
720
|
-
|
|
721
|
-
---
|
|
722
|
-
|
|
723
|
-
### 📌 Available Commands
|
|
724
|
-
|
|
725
|
-
```sh
|
|
726
|
-
$ npx forge-sql-orm --help
|
|
727
|
-
|
|
728
|
-
Usage: forge-sql-orm [options] [command]
|
|
225
|
+
### Complex Queries with Aggregations
|
|
729
226
|
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
227
|
+
```js
|
|
228
|
+
// Finding duplicates
|
|
229
|
+
const duplicates = await forgeSQL
|
|
230
|
+
.fetch()
|
|
231
|
+
.executeQuery(
|
|
232
|
+
forgeSQL
|
|
233
|
+
.getDrizzleQueryBuilder()
|
|
234
|
+
.select({
|
|
235
|
+
name: Users.name,
|
|
236
|
+
count: rawSql`COUNT(*) as \`count\``
|
|
237
|
+
}).from(Users)
|
|
238
|
+
.groupBy(Users.name)
|
|
239
|
+
.having(rawSql`COUNT(*) > 1`)
|
|
240
|
+
);
|
|
241
|
+
// Returns: { name: "John Doe", count: 2 }
|
|
733
242
|
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
243
|
+
// Using executeQueryOnlyOne for unique results
|
|
244
|
+
const userStats = await forgeSQL
|
|
245
|
+
.fetch()
|
|
246
|
+
.executeQueryOnlyOne(
|
|
247
|
+
forgeSQL
|
|
248
|
+
.getDrizzleQueryBuilder()
|
|
249
|
+
.select({
|
|
250
|
+
totalUsers: rawSql`COUNT(*) as \`totalUsers\``,
|
|
251
|
+
uniqueNames: rawSql`COUNT(DISTINCT name) as \`uniqueNames\``
|
|
252
|
+
}).from(Users)
|
|
253
|
+
);
|
|
254
|
+
// Returns: { totalUsers: 100, uniqueNames: 80 }
|
|
255
|
+
// Throws error if multiple records found
|
|
740
256
|
```
|
|
741
257
|
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
### 📌 Entity Generation
|
|
258
|
+
### Raw SQL Queries
|
|
745
259
|
|
|
746
|
-
```
|
|
747
|
-
|
|
260
|
+
```js
|
|
261
|
+
// Using executeRawSQL for direct SQL queries
|
|
262
|
+
const users = await forgeSQL
|
|
263
|
+
.fetch()
|
|
264
|
+
.executeRawSQL<Users>("SELECT * FROM users");
|
|
748
265
|
```
|
|
749
266
|
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
- Connect to `mydb` on `localhost:3306`.
|
|
753
|
-
- Generate MikroORM entity classes.
|
|
754
|
-
- Save them in `./src/database/entities`.
|
|
755
|
-
- Create an `index.ts` file with all entities.
|
|
756
|
-
- **`--versionField updatedAt`**: Specifies the field used for entity versioning.
|
|
757
|
-
- **`--saveEnv`**: Saves configuration settings to `.env` for future use.
|
|
758
|
-
|
|
759
|
-
#### 🔹 VersionField Explanation
|
|
760
|
-
|
|
761
|
-
The `--versionField` option is crucial for handling entity versioning. It should be a field of type `datetime`, `integer`, or `decimal`. This field is used to track changes to entities, ensuring that updates follow proper versioning strategies.
|
|
762
|
-
|
|
763
|
-
**Example:**
|
|
764
|
-
|
|
765
|
-
- `updatedAt` (datetime) - Commonly used for timestamp-based versioning.
|
|
766
|
-
- `versionNumber` (integer) - Can be used for numeric version increments.
|
|
767
|
-
|
|
768
|
-
If the specified field does not meet the required criteria, warnings will be logged.
|
|
267
|
+
## CRUD Operations
|
|
769
268
|
|
|
770
|
-
|
|
269
|
+
### Insert Operations
|
|
771
270
|
|
|
772
|
-
|
|
271
|
+
```js
|
|
272
|
+
// Single insert
|
|
273
|
+
const userId = await forgeSQL.crud().insert(Users, [{ id: 1, name: "Smith" }]);
|
|
773
274
|
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
275
|
+
// Bulk insert
|
|
276
|
+
await forgeSQL.crud().insert(Users, [
|
|
277
|
+
{ id: 2, name: "Smith" },
|
|
278
|
+
{ id: 3, name: "Vasyl" },
|
|
279
|
+
]);
|
|
777
280
|
|
|
778
|
-
|
|
281
|
+
// Insert with duplicate handling
|
|
282
|
+
await forgeSQL.crud().insert(
|
|
283
|
+
Users,
|
|
284
|
+
[
|
|
285
|
+
{ id: 4, name: "Smith" },
|
|
286
|
+
{ id: 4, name: "Vasyl" },
|
|
287
|
+
],
|
|
288
|
+
true
|
|
289
|
+
);
|
|
290
|
+
```
|
|
779
291
|
|
|
780
|
-
|
|
781
|
-
- Save migration files in `./src/database/migration`.
|
|
782
|
-
- Create `index.ts` for automatic migration execution.
|
|
783
|
-
- **`--saveEnv`**: Saves configuration settings to `.env` for future use.
|
|
292
|
+
### Update Operations
|
|
784
293
|
|
|
785
|
-
|
|
294
|
+
```js
|
|
295
|
+
// Update by ID with optimistic locking
|
|
296
|
+
await forgeSQL.crud().updateById({ id: 1, name: "Smith Updated" }, Users);
|
|
786
297
|
|
|
787
|
-
|
|
298
|
+
// Update specific fields
|
|
299
|
+
await forgeSQL.crud().updateById(
|
|
300
|
+
{ id: 1, age: 35 },
|
|
301
|
+
Users
|
|
302
|
+
);
|
|
788
303
|
|
|
789
|
-
|
|
790
|
-
|
|
304
|
+
// Update with custom WHERE condition
|
|
305
|
+
await forgeSQL.crud().updateFields(
|
|
306
|
+
{ name: "New Name", age: 35 },
|
|
307
|
+
Users,
|
|
308
|
+
eq(Users.email, "smith@example.com")
|
|
309
|
+
);
|
|
791
310
|
```
|
|
792
311
|
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
- Detect schema changes (new tables, columns, indexes).
|
|
796
|
-
- Generate only the required migrations.
|
|
797
|
-
- Update `index.ts` to include new migrations.
|
|
798
|
-
- **`--saveEnv`**: Saves configuration settings to `.env` for future use.
|
|
799
|
-
|
|
800
|
-
---
|
|
801
|
-
|
|
802
|
-
### 📌 Using the patch:mikroorm Command
|
|
803
|
-
|
|
804
|
-
If needed, you can manually apply the patch at any time using:
|
|
312
|
+
### Delete Operations
|
|
805
313
|
|
|
806
|
-
```
|
|
807
|
-
|
|
314
|
+
```js
|
|
315
|
+
// Delete by ID
|
|
316
|
+
await forgeSQL.crud().deleteById(1, Users);
|
|
808
317
|
```
|
|
809
318
|
|
|
810
|
-
|
|
319
|
+
## Optimistic Locking
|
|
811
320
|
|
|
812
|
-
|
|
813
|
-
- Fixes dynamic imports to work in Forge.
|
|
814
|
-
- Ensures Knex and MikroORM work properly inside Forge.
|
|
321
|
+
Optimistic locking is a concurrency control mechanism that prevents data conflicts when multiple transactions attempt to update the same record concurrently. Instead of using locks, this technique relies on a version field in your entity models.
|
|
815
322
|
|
|
816
|
-
|
|
323
|
+
### Supported Version Field Types
|
|
817
324
|
|
|
818
|
-
|
|
325
|
+
- `datetime` - Timestamp-based versioning
|
|
326
|
+
- `timestamp` - Timestamp-based versioning
|
|
327
|
+
- `integer` - Numeric version increment
|
|
328
|
+
- `decimal` - Numeric version increment
|
|
819
329
|
|
|
820
|
-
|
|
330
|
+
### Configuration
|
|
821
331
|
|
|
822
|
-
|
|
332
|
+
```typescript
|
|
333
|
+
const options = {
|
|
334
|
+
additionalMetadata: {
|
|
335
|
+
users: {
|
|
336
|
+
tableName: "users",
|
|
337
|
+
versionField: {
|
|
338
|
+
fieldName: "updatedAt",
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
};
|
|
823
343
|
|
|
824
|
-
|
|
825
|
-
--host, --port, --user, --password, --dbName, --output, --versionField, --saveEnv
|
|
344
|
+
const forgeSQL = new ForgeSQL(options);
|
|
826
345
|
```
|
|
827
346
|
|
|
828
|
-
|
|
347
|
+
### Example Usage
|
|
829
348
|
|
|
830
|
-
```
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
349
|
+
```typescript
|
|
350
|
+
// The version field will be automatically handled
|
|
351
|
+
await forgeSQL.crud().updateById(
|
|
352
|
+
{
|
|
353
|
+
id: 1,
|
|
354
|
+
name: "Updated Name",
|
|
355
|
+
updatedAt: new Date() // Will be automatically set if not provided
|
|
356
|
+
},
|
|
357
|
+
Users
|
|
358
|
+
);
|
|
836
359
|
```
|
|
837
360
|
|
|
838
|
-
|
|
361
|
+
## ForgeSqlOrmOptions
|
|
839
362
|
|
|
840
|
-
|
|
841
|
-
FORGE_SQL_ORM_HOST=localhost
|
|
842
|
-
FORGE_SQL_ORM_PORT=3306
|
|
843
|
-
FORGE_SQL_ORM_USER=root
|
|
844
|
-
FORGE_SQL_ORM_PASSWORD=secret
|
|
845
|
-
FORGE_SQL_ORM_DBNAME=mydb
|
|
846
|
-
```
|
|
363
|
+
The `ForgeSqlOrmOptions` object allows customization of ORM behavior:
|
|
847
364
|
|
|
848
|
-
|
|
365
|
+
| Option | Type | Description |
|
|
366
|
+
| -------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
367
|
+
| `logRawSqlQuery` | `boolean` | Enables logging of raw SQL queries in the Atlassian Forge Developer Console. Useful for debugging and monitoring. Defaults to `false`. |
|
|
368
|
+
| `disableOptimisticLocking` | `boolean` | Disables optimistic locking. When set to `true`, no additional condition (e.g., a version check) is added during record updates, which can improve performance. However, this may lead to conflicts when multiple transactions attempt to update the same record concurrently. |
|
|
369
|
+
| `additionalMetadata` | `object` | Allows adding custom metadata to all entities. This is useful for tracking common fields across all tables (e.g., `createdAt`, `updatedAt`, `createdBy`, etc.). The metadata will be automatically added to all generated entities. |
|
|
849
370
|
|
|
850
|
-
|
|
371
|
+
## CLI Commands
|
|
851
372
|
|
|
852
|
-
|
|
373
|
+
```sh
|
|
374
|
+
$ npx forge-sql-orm --help
|
|
853
375
|
|
|
854
|
-
|
|
376
|
+
Usage: forge-sql-orm [options] [command]
|
|
855
377
|
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
378
|
+
Options:
|
|
379
|
+
-V, --version Output the version number
|
|
380
|
+
-h, --help Display help for command
|
|
859
381
|
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
382
|
+
Commands:
|
|
383
|
+
generate:model [options] Generate Drizzle models from the database
|
|
384
|
+
migrations:create [options] Generate an initial migration for the entire database
|
|
385
|
+
migrations:update [options] Generate a migration to update the database schema
|
|
386
|
+
migrations:drop [options] Generate a migration to drop all tables
|
|
387
|
+
help [command] Display help for a specific command
|
|
863
388
|
```
|
|
864
389
|
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
---
|
|
868
|
-
|
|
869
|
-
📜 **License**
|
|
390
|
+
## License
|
|
870
391
|
This project is licensed under the **MIT License**.
|
|
871
392
|
Feel free to use it for commercial and personal projects.
|