forge-sql-orm 1.0.23 → 1.0.25

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
 
@@ -173,50 +182,86 @@ const results = await forgeSQL.fetch().executeSchemaSQL(query, innerJoinSchema);
173
182
  console.log(results);
174
183
  ```
175
184
 
176
- 🛠 CRUD Operations
177
-
178
- - Insert Data
179
-
180
- ```js
181
- // INSERT INTO users (id, name) VALUES (1,'Smith')
182
- const userId = await forgeSQL.crud().insert(UsersSchema, [{ id: 1, name: "Smith" }]);
183
- ```
184
-
185
- - Insert Bulk Data
186
-
187
- ```js
188
- // INSERT INTO users (id,name) VALUES (2,'Smith'), (3,'Vasyl')
189
- await forgeSQL.crud().insert(UsersSchema, [
190
- { id: 2, name: "Smith" },
191
- { id: 3, name: "Vasyl" },
192
- ]);
193
- ```
194
-
195
- - Insert Data with duplicates
196
-
197
- ```js
198
- // INSERT INTO users (id,name) VALUES (4,'Smith'), (4, 'Vasyl') ON DUPLICATE KEY UPDATE name = VALUES(name)
199
- await forgeSQL.crud().insert(
200
- UsersSchema,
201
- [
202
- { id: 4, name: "Smith" },
203
- { id: 4, name: "Vasyl" },
204
- ],
205
- true,
206
- );
207
- ```
208
-
209
- - Update Data
210
-
211
- ```js
212
- await forgeSQL.crud().updateById({ id: 1, name: "Smith Updated" }, UsersSchema);
213
- ```
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:
214
186
 
215
- - Delete Data
187
+ ---
216
188
 
217
- ```js
218
- await forgeSQL.crud().deleteById(1, UsersSchema);
219
- ```
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
+ ```
220
265
 
221
266
  ## Quick Start
222
267
 
@@ -495,6 +540,130 @@ console.log(results);
495
540
 
496
541
  ---
497
542
 
543
+ ## ForgeSqlOrmOptions
544
+
545
+ The `ForgeSqlOrmOptions` object allows customization of ORM behavior. Currently, it supports the following options:
546
+
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
+ ---
553
+
554
+ ### Example: Initializing `ForgeSQL` with Options
555
+
556
+ ```typescript
557
+ import ForgeSQL from "forge-sql-orm";
558
+ import { Orders } from "./entities/Orders";
559
+ import { Users } from "./entities/Users";
560
+ import ENTITIES from "./entities";
561
+
562
+ const options = {
563
+ logRawSqlQuery: true, // Enable query logging for debugging purposes
564
+ };
565
+
566
+ const forgeSQL = new ForgeSQL(ENTITIES, options);
567
+ ```
568
+
569
+ ## Using `getKnex()` for Advanced SQL Queries
570
+
571
+ The `getKnex()` method allows direct interaction with Knex.js, enabling execution of raw SQL queries and complex query building.
572
+
573
+ ### Example: Finding Duplicate Records in `UsersSchema`
574
+
575
+ ```typescript
576
+ const fields: string[] = ["name", "email"];
577
+
578
+ // Define selected fields, including a count of duplicate occurrences
579
+ const selectFields: Array<string | Knex.Raw> = [
580
+ ...fields,
581
+ forgeSQL.getKnex().raw("COUNT(*) as count"),
582
+ ];
583
+
584
+ // Create a QueryBuilder with grouping and filtering for duplicates
585
+ let selectQueryBuilder = forgeSQL
586
+ .createQueryBuilder(UsersSchema)
587
+ .select(selectFields as unknown as string[])
588
+ .groupBy(fields)
589
+ .having("COUNT(*) > 1");
590
+
591
+ // Generate the final SQL query with ordering by count
592
+ const query = selectQueryBuilder.getKnexQuery().orderByRaw("count ASC").toSQL().sql;
593
+
594
+ /*
595
+ SQL Query:
596
+ SELECT `u0`.`name`, `u0`.`email`, COUNT(*) as count
597
+ FROM `users` AS `u0`
598
+ GROUP BY `u0`.`name`, `u0`.`email`
599
+ HAVING COUNT(*) > 1
600
+ ORDER BY count ASC;
601
+ */
602
+
603
+ // Execute the SQL query and retrieve results
604
+ const duplicateResult = await forgeSQL
605
+ .fetch()
606
+ .executeSchemaSQL<DuplicateResult>(query, DuplicateSchema);
607
+ ```
608
+
609
+ 🔹 **What does this example do?**
610
+
611
+ 1. Selects `name` and `email`, along with the count of duplicate occurrences (`COUNT(*) as count`).
612
+ 2. Groups the data by `name` and `email` to identify duplicates.
613
+ 3. Filters the results to include only groups with more than one record (`HAVING COUNT(*) > 1`).
614
+ 4. Sorts the final results in ascending order by count (`ORDER BY count ASC`).
615
+ 5. Executes the SQL query and returns the duplicate records.
616
+
617
+ ---
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
+
498
667
  ## Usage with MikroORM Generator
499
668
 
500
669
  If you prefer to use MikroORM's default entity generator, then manually import your entities:
@@ -505,9 +674,13 @@ import { UserEntity, TaskEntity } from "./entities";
505
674
 
506
675
  ---
507
676
 
508
- ## The CLI provides commands to generate models and manage migrations.
677
+ ## Forge SQL ORM CLI Documentation
509
678
 
510
- 🔹 Available Commands
679
+ The CLI provides commands to generate models and manage migrations for MikroORM in Forge.
680
+
681
+ ---
682
+
683
+ ### 📌 Available Commands
511
684
 
512
685
  ```sh
513
686
  $ npx forge-sql-orm --help
@@ -515,58 +688,79 @@ $ npx forge-sql-orm --help
515
688
  Usage: forge-sql-orm [options] [command]
516
689
 
517
690
  Options:
518
- -V, --version output the version number
519
- -h, --help display help for command
691
+ -V, --version Output the version number
692
+ -h, --help Display help for command
520
693
 
521
694
  Commands:
522
695
  generate:model [options] Generate MikroORM models from the database.
523
696
  migrations:create [options] Generate an initial migration for the entire database.
524
697
  migrations:update [options] Generate a migration to update the database schema.
525
- patch:mikroorm Patch MikroORM and Knex dependencies to work properly with Forge
526
- 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.
527
700
  ```
528
701
 
529
- 📌 Entity Generation
702
+ ---
703
+
704
+ ### 📌 Entity Generation
530
705
 
531
706
  ```sh
532
- 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
533
708
  ```
534
709
 
535
710
  This command will:
536
711
 
537
- - Connect to mydb on localhost:3306
538
- - Generate MikroORM entity classes
539
- - Save them in ./src/database/entities
540
- - 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:**
541
724
 
542
- 📌 Database Migrations
725
+ - `updatedAt` (datetime) - Commonly used for timestamp-based versioning.
726
+ - `versionNumber` (integer) - Can be used for numeric version increments.
727
+
728
+ If the specified field does not meet the required criteria, warnings will be logged.
729
+
730
+ ---
731
+
732
+ ### 📌 Database Migrations
543
733
 
544
734
  ```sh
545
- 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
546
736
  ```
547
737
 
548
738
  This command will:
549
739
 
550
- - Create initial migration from all detected entities
551
- - Save migration files in ./src/database/migration
552
- - Create migrationCount.ts to track versions
553
- - 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.
554
744
 
555
- 📌 Update Schema Migration
745
+ ---
746
+
747
+ ### 📌 Update Schema Migration
556
748
 
557
749
  ```sh
558
- 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
559
751
  ```
560
752
 
561
753
  This command will:
562
754
 
563
- 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
+ ---
564
761
 
565
- - Generate only required migrations
566
- - Increment migrationCount.ts
567
- - Update index.ts to include new migrations
762
+ ### 📌 Using the patch:mikroorm Command
568
763
 
569
- 📌 Using the patch:mikroorm Command
570
764
  If needed, you can manually apply the patch at any time using:
571
765
 
572
766
  ```sh
@@ -579,23 +773,19 @@ This command:
579
773
  - Fixes dynamic imports to work in Forge.
580
774
  - Ensures Knex and MikroORM work properly inside Forge.
581
775
 
582
- 📌 Manual Migration Execution
583
- To manually execute migrations in your application:
776
+ ---
584
777
 
585
- ```js
586
- import migrationRunner from "./src/database/migration";
587
- import { MigrationRunner } from "@forge/sql/out/migration";
778
+ ### 📌 Configuration Methods
588
779
 
589
- const runner = new MigrationRunner();
590
- await migrationRunner(runner);
591
- await runner.run(); // ✅ Apply migrations
592
- ```
593
-
594
- 🔧 Configuration
595
780
  You can define database credentials using:
596
781
 
597
- 1. Command-line arguments: --host, --port, etc.
598
- 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**:
599
789
 
600
790
  ```bash
601
791
  export FORGE_SQL_ORM_HOST=localhost
@@ -605,7 +795,7 @@ export FORGE_SQL_ORM_PASSWORD=secret
605
795
  export FORGE_SQL_ORM_DBNAME=mydb
606
796
  ```
607
797
 
608
- 3. use .env
798
+ 3️⃣ **Using a `.env` file**:
609
799
 
610
800
  ```sh
611
801
  FORGE_SQL_ORM_HOST=localhost
@@ -615,7 +805,24 @@ FORGE_SQL_ORM_PASSWORD=secret
615
805
  FORGE_SQL_ORM_DBNAME=mydb
616
806
  ```
617
807
 
618
- 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.
619
826
 
620
827
  ---
621
828