forge-sql-orm 1.0.24 → 1.0.26

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 CHANGED
@@ -1,5 +1,7 @@
1
1
  # Forge SQL ORM
2
2
 
3
+ [![forge-sql-orm CI](https://github.com/vzakharchenko/forge-sql-orm/actions/workflows/node.js.yml/badge.svg)](https://github.com/vzakharchenko/forge-sql-orm/actions/workflows/node.js.yml)
4
+
3
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 [MikroORM](https://mikro-orm.io/docs/query-builder) and provides advanced capabilities for working with relational databases inside Forge.
4
6
 
5
7
  ## Key Features
@@ -10,6 +12,13 @@
10
12
  - ✅ **Schema migration support**, allowing automatic schema evolution.
11
13
  - ✅ **Automatic entity generation** from MySQL/tidb databases.
12
14
  - ✅ **Automatic migration generation** from MySQL/tidb databases.
15
+ - ✅ **Optimistic Locking** Ensures data consistency by preventing conflicts when multiple users update the same record.
16
+
17
+ 🚀 **Development in Progress** 🚀
18
+ I am currently working on implementing the following features:
19
+ - 🗑️ **Soft Deletion Support** – Allows marking records as deleted without actually removing them from the database, enabling easy recovery.
20
+ - 🏗️ **Complex Query Handling** _(JOIN, GROUP BY, etc.) without requiring an EntitySchema_ – Simplifies the execution of advanced SQL queries without the need to define additional schemas.
21
+ ---
13
22
 
14
23
  ## Installation
15
24
 
@@ -125,8 +134,6 @@ import ENTITIES from "./entities";
125
134
  const forgeSQL = new ForgeSQL(ENTITIES);
126
135
  ```
127
136
 
128
-
129
-
130
137
  - Fetch Data:
131
138
 
132
139
  ```js
@@ -175,50 +182,86 @@ const results = await forgeSQL.fetch().executeSchemaSQL(query, innerJoinSchema);
175
182
  console.log(results);
176
183
  ```
177
184
 
178
- 🛠 CRUD Operations
179
-
180
- - Insert Data
181
-
182
- ```js
183
- // INSERT INTO users (id, name) VALUES (1,'Smith')
184
- const userId = await forgeSQL.crud().insert(UsersSchema, [{ id: 1, name: "Smith" }]);
185
- ```
186
-
187
- - Insert Bulk Data
188
-
189
- ```js
190
- // INSERT INTO users (id,name) VALUES (2,'Smith'), (3,'Vasyl')
191
- await forgeSQL.crud().insert(UsersSchema, [
192
- { id: 2, name: "Smith" },
193
- { id: 3, name: "Vasyl" },
194
- ]);
195
- ```
185
+ Below is an example of how you can extend your README's CRUD Operations section with information and examples for both `updateFieldById` and `updateFields` methods:
196
186
 
197
- - Insert Data with duplicates
198
-
199
- ```js
200
- // INSERT INTO users (id,name) VALUES (4,'Smith'), (4, 'Vasyl') ON DUPLICATE KEY UPDATE name = VALUES(name)
201
- await forgeSQL.crud().insert(
202
- UsersSchema,
203
- [
204
- { id: 4, name: "Smith" },
205
- { id: 4, name: "Vasyl" },
206
- ],
207
- true,
208
- );
209
- ```
210
-
211
- - Update Data
212
-
213
- ```js
214
- await forgeSQL.crud().updateById({ id: 1, name: "Smith Updated" }, UsersSchema);
215
- ```
216
-
217
- - Delete Data
187
+ ---
218
188
 
219
- ```js
220
- await forgeSQL.crud().deleteById(1, UsersSchema);
221
- ```
189
+ 🛠 **CRUD Operations**
190
+
191
+ - **Insert Data**
192
+
193
+ ```js
194
+ // INSERT INTO users (id, name) VALUES (1, 'Smith')
195
+ const userId = await forgeSQL.crud().insert(UsersSchema, [{ id: 1, name: "Smith" }]);
196
+ ```
197
+
198
+ - **Insert Bulk Data**
199
+
200
+ ```js
201
+ // INSERT INTO users (id, name) VALUES (2, 'Smith'), (3, 'Vasyl')
202
+ await forgeSQL.crud().insert(UsersSchema, [
203
+ { id: 2, name: "Smith" },
204
+ { id: 3, name: "Vasyl" },
205
+ ]);
206
+ ```
207
+
208
+ - **Insert Data with Duplicates**
209
+
210
+ ```js
211
+ // INSERT INTO users (id, name) VALUES (4, 'Smith'), (4, 'Vasyl')
212
+ // ON DUPLICATE KEY UPDATE name = VALUES(name)
213
+ await forgeSQL.crud().insert(
214
+ UsersSchema,
215
+ [
216
+ { id: 4, name: "Smith" },
217
+ { id: 4, name: "Vasyl" },
218
+ ],
219
+ true,
220
+ );
221
+ ```
222
+
223
+ - **Update Data by Primary Key**
224
+
225
+ ```js
226
+ // This uses the updateById method which wraps updateFieldById (with optimistic locking if configured)
227
+ await forgeSQL.crud().updateById({ id: 1, name: "Smith Updated" }, UsersSchema);
228
+ ```
229
+
230
+ - **Update Specific Fields by Primary Key**
231
+
232
+ ```js
233
+ // Updates specific fields of a record identified by its primary key.
234
+ // Note: The primary key field (e.g. id) must be included in the fields array.
235
+ await forgeSQL.crud().updateFieldById({ id: 1, name: "Updated Name" }, ["id", "name"], UsersSchema);
236
+ ```
237
+
238
+ - **Update Fields Without Primary Key and Versioning**
239
+
240
+ ```js
241
+ // Updates specified fields for records matching the given conditions.
242
+ // In this example, the "name" and "age" fields are updated for users where the email is 'smith@example.com'.
243
+ const affectedRows = await forgeSQL.crud().updateFields(
244
+ { name: "New Name", age: 35, email: "smith@example.com" },
245
+ ["name", "age"],
246
+ UsersSchema
247
+ );
248
+ console.log(`Rows affected: ${affectedRows}`);
249
+
250
+ // Alternatively, you can provide an explicit WHERE condition:
251
+ const affectedRowsWithWhere = await forgeSQL.crud().updateFields(
252
+ { name: "New Name", age: 35 },
253
+ ["name", "age"],
254
+ UsersSchema,
255
+ { email: "smith@example.com" }
256
+ );
257
+ console.log(`Rows affected: ${affectedRowsWithWhere}`);
258
+ ```
259
+
260
+ - **Delete Data**
261
+
262
+ ```js
263
+ await forgeSQL.crud().deleteById(1, UsersSchema);
264
+ ```
222
265
 
223
266
  ## Quick Start
224
267
 
@@ -501,9 +544,12 @@ console.log(results);
501
544
 
502
545
  The `ForgeSqlOrmOptions` object allows customization of ORM behavior. Currently, it supports the following options:
503
546
 
504
- | Option | Type | Description |
505
- |------------------|---------|-------------|
506
- | `logRawSqlQuery` | `boolean` | Enables logging of SQL queries in the Atlassian Forge Developer Console. Useful for debugging and monitoring. Defaults to `false`. |
547
+ | Option | Type | Description |
548
+ | -------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
549
+ | `logRawSqlQuery` | `boolean` | Enables logging of raw SQL queries in the Atlassian Forge Developer Console. Useful for debugging and monitoring. Defaults to `false`. |
550
+ | `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. |
551
+
552
+ ---
507
553
 
508
554
  ### Example: Initializing `ForgeSQL` with Options
509
555
 
@@ -520,9 +566,7 @@ const options = {
520
566
  const forgeSQL = new ForgeSQL(ENTITIES, options);
521
567
  ```
522
568
 
523
-
524
-
525
- ## Using `getKnex()` for Advanced SQL Queries
569
+ ## Using `getKnex()` for Advanced SQL Queries
526
570
 
527
571
  The `getKnex()` method allows direct interaction with Knex.js, enabling execution of raw SQL queries and complex query building.
528
572
 
@@ -545,10 +589,7 @@ let selectQueryBuilder = forgeSQL
545
589
  .having("COUNT(*) > 1");
546
590
 
547
591
  // Generate the final SQL query with ordering by count
548
- const query = selectQueryBuilder
549
- .getKnexQuery()
550
- .orderByRaw("count ASC")
551
- .toSQL().sql;
592
+ const query = selectQueryBuilder.getKnexQuery().orderByRaw("count ASC").toSQL().sql;
552
593
 
553
594
  /*
554
595
  SQL Query:
@@ -566,6 +607,7 @@ const duplicateResult = await forgeSQL
566
607
  ```
567
608
 
568
609
  🔹 **What does this example do?**
610
+
569
611
  1. Selects `name` and `email`, along with the count of duplicate occurrences (`COUNT(*) as count`).
570
612
  2. Groups the data by `name` and `email` to identify duplicates.
571
613
  3. Filters the results to include only groups with more than one record (`HAVING COUNT(*) > 1`).
@@ -574,6 +616,54 @@ const duplicateResult = await forgeSQL
574
616
 
575
617
  ---
576
618
 
619
+ Below is the plain text version of the additional section for Optimistic Locking. You can copy and paste it directly into your README:
620
+
621
+ ---
622
+
623
+ ## Optimistic Locking
624
+
625
+ 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.
626
+
627
+ ### How It Works
628
+
629
+ - **Version Field:**
630
+ 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`.
631
+
632
+ - **Supported Types:**
633
+ 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.
634
+
635
+ - **Automatic Configuration:**
636
+ When generating models, you can specify a field (e.g., `updatedAt`) that automatically becomes the version field by using the `--versionField` flag. For example:
637
+ ```sh
638
+ npx forge-sql-orm generate:model --versionField updatedAt
639
+ ```
640
+ In this case, any model that includes a field named `updatedAt` meeting the required conditions will have it configured for optimistic locking.
641
+
642
+ ### Example
643
+
644
+ Here’s how you can define an entity with optimistic locking using MikroORM:
645
+
646
+ ```js
647
+ export class TestEntityVersion {
648
+ id!: number;
649
+ name?: string;
650
+ version!: number;
651
+ }
652
+
653
+ export const TestEntityVersionSchema = new EntitySchema({
654
+ class: TestEntityVersion,
655
+ properties: {
656
+ id: { primary: true, type: "integer", unsigned: false, autoincrement: false },
657
+ name: { type: "string", nullable: true },
658
+ version: { type: "integer", nullable: false, version: true },
659
+ },
660
+ });
661
+ ```
662
+
663
+ 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.
664
+
665
+ ---
666
+
577
667
  ## Usage with MikroORM Generator
578
668
 
579
669
  If you prefer to use MikroORM's default entity generator, then manually import your entities:
@@ -584,9 +674,13 @@ import { UserEntity, TaskEntity } from "./entities";
584
674
 
585
675
  ---
586
676
 
587
- ## The CLI provides commands to generate models and manage migrations.
677
+ ## Forge SQL ORM CLI Documentation
678
+
679
+ The CLI provides commands to generate models and manage migrations for MikroORM in Forge.
680
+
681
+ ---
588
682
 
589
- 🔹 Available Commands
683
+ ### 📌 Available Commands
590
684
 
591
685
  ```sh
592
686
  $ npx forge-sql-orm --help
@@ -594,58 +688,79 @@ $ npx forge-sql-orm --help
594
688
  Usage: forge-sql-orm [options] [command]
595
689
 
596
690
  Options:
597
- -V, --version output the version number
598
- -h, --help display help for command
691
+ -V, --version Output the version number
692
+ -h, --help Display help for command
599
693
 
600
694
  Commands:
601
695
  generate:model [options] Generate MikroORM models from the database.
602
696
  migrations:create [options] Generate an initial migration for the entire database.
603
697
  migrations:update [options] Generate a migration to update the database schema.
604
- patch:mikroorm Patch MikroORM and Knex dependencies to work properly with Forge
605
- help [command] display help for command
698
+ patch:mikroorm Patch MikroORM and Knex dependencies to work properly with Forge.
699
+ help [command] Display help for a specific command.
606
700
  ```
607
701
 
608
- 📌 Entity Generation
702
+ ---
703
+
704
+ ### 📌 Entity Generation
609
705
 
610
706
  ```sh
611
- npx forge-sql-orm generate:model --host localhost --port 3306 --user root --password secret --dbName mydb --output ./src/database/entities
707
+ npx forge-sql-orm generate:model --host localhost --port 3306 --user root --password secret --dbName mydb --output ./src/database/entities --versionField updatedAt --saveEnv
612
708
  ```
613
709
 
614
710
  This command will:
615
711
 
616
- - Connect to mydb on localhost:3306
617
- - Generate MikroORM entity classes
618
- - Save them in ./src/database/entities
619
- - Create an index.ts file with all entities
712
+ - Connect to `mydb` on `localhost:3306`.
713
+ - Generate MikroORM entity classes.
714
+ - Save them in `./src/database/entities`.
715
+ - Create an `index.ts` file with all entities.
716
+ - **`--versionField updatedAt`**: Specifies the field used for entity versioning.
717
+ - **`--saveEnv`**: Saves configuration settings to `.env` for future use.
718
+
719
+ #### 🔹 VersionField Explanation
720
+
721
+ 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.
722
+
723
+ **Example:**
724
+
725
+ - `updatedAt` (datetime) - Commonly used for timestamp-based versioning.
726
+ - `versionNumber` (integer) - Can be used for numeric version increments.
620
727
 
621
- 📌 Database Migrations
728
+ If the specified field does not meet the required criteria, warnings will be logged.
729
+
730
+ ---
731
+
732
+ ### 📌 Database Migrations
622
733
 
623
734
  ```sh
624
- npx forge-sql-orm migrations:create --host localhost --port 3306 --user root --password secret --dbName mydb --output ./src/database/migration --entitiesPath ./src/database/entities
735
+ npx forge-sql-orm migrations:create --host localhost --port 3306 --user root --password secret --dbName mydb --output ./src/database/migration --entitiesPath ./src/database/entities --saveEnv
625
736
  ```
626
737
 
627
738
  This command will:
628
739
 
629
- - Create initial migration from all detected entities
630
- - Save migration files in ./src/database/migration
631
- - Create migrationCount.ts to track versions
632
- - Create index.ts for automatic migration execution
740
+ - Create the initial migration based on all detected entities.
741
+ - Save migration files in `./src/database/migration`.
742
+ - Create `index.ts` for automatic migration execution.
743
+ - **`--saveEnv`**: Saves configuration settings to `.env` for future use.
633
744
 
634
- 📌 Update Schema Migration
745
+ ---
746
+
747
+ ### 📌 Update Schema Migration
635
748
 
636
749
  ```sh
637
- npx forge-sql-orm migrations:update --host localhost --port 3306 --user root --password secret --dbName mydb --output ./src/database/migration --entitiesPath ./src/database/entities
750
+ npx forge-sql-orm migrations:update --host localhost --port 3306 --user root --password secret --dbName mydb --output ./src/database/migration --entitiesPath ./src/database/entities --saveEnv
638
751
  ```
639
752
 
640
753
  This command will:
641
754
 
642
- Detect schema changes (new tables, columns, indexes)
755
+ - Detect schema changes (new tables, columns, indexes).
756
+ - Generate only the required migrations.
757
+ - Update `index.ts` to include new migrations.
758
+ - **`--saveEnv`**: Saves configuration settings to `.env` for future use.
759
+
760
+ ---
643
761
 
644
- - Generate only required migrations
645
- - Increment migrationCount.ts
646
- - Update index.ts to include new migrations
762
+ ### 📌 Using the patch:mikroorm Command
647
763
 
648
- 📌 Using the patch:mikroorm Command
649
764
  If needed, you can manually apply the patch at any time using:
650
765
 
651
766
  ```sh
@@ -658,23 +773,19 @@ This command:
658
773
  - Fixes dynamic imports to work in Forge.
659
774
  - Ensures Knex and MikroORM work properly inside Forge.
660
775
 
661
- 📌 Manual Migration Execution
662
- To manually execute migrations in your application:
663
-
664
- ```js
665
- import migrationRunner from "./src/database/migration";
666
- import { MigrationRunner } from "@forge/sql/out/migration";
776
+ ---
667
777
 
668
- const runner = new MigrationRunner();
669
- await migrationRunner(runner);
670
- await runner.run(); // ✅ Apply migrations
671
- ```
778
+ ### 📌 Configuration Methods
672
779
 
673
- 🔧 Configuration
674
780
  You can define database credentials using:
675
781
 
676
- 1. Command-line arguments: --host, --port, etc.
677
- 2️. Environment variables:
782
+ 1️⃣ **Command-line arguments**:
783
+
784
+ ```sh
785
+ --host, --port, --user, --password, --dbName, --output, --versionField, --saveEnv
786
+ ```
787
+
788
+ 2️⃣ **Environment variables**:
678
789
 
679
790
  ```bash
680
791
  export FORGE_SQL_ORM_HOST=localhost
@@ -684,7 +795,7 @@ export FORGE_SQL_ORM_PASSWORD=secret
684
795
  export FORGE_SQL_ORM_DBNAME=mydb
685
796
  ```
686
797
 
687
- 3. use .env
798
+ 3️⃣ **Using a `.env` file**:
688
799
 
689
800
  ```sh
690
801
  FORGE_SQL_ORM_HOST=localhost
@@ -694,7 +805,24 @@ FORGE_SQL_ORM_PASSWORD=secret
694
805
  FORGE_SQL_ORM_DBNAME=mydb
695
806
  ```
696
807
 
697
- 4. Interactive prompts (if missing parameters)
808
+ 4️⃣ **Interactive prompts** (if missing parameters, the CLI will ask for input).
809
+
810
+ ---
811
+
812
+ ### 📌 Manual Migration Execution
813
+
814
+ To manually execute migrations in your application:
815
+
816
+ ```js
817
+ import migrationRunner from "./src/database/migration";
818
+ import { MigrationRunner } from "@forge/sql/out/migration";
819
+
820
+ const runner = new MigrationRunner();
821
+ await migrationRunner(runner);
822
+ await runner.run(); // ✅ Apply migrations
823
+ ```
824
+
825
+ This approach allows you to apply migrations programmatically in a Forge application.
698
826
 
699
827
  ---
700
828