@mlagie/sql-connector 1.4.9 → 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/.github/ISSUE_TEMPLATE/bug_report.md +43 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +29 -0
- package/.github/workflows/publish.yml +47 -0
- package/README.md +260 -48
- package/docs/fr/README.md +204 -24
- package/eslint.config.mjs +12 -0
- package/index.d.ts +15 -45
- package/package.json +7 -3
- package/releases/1.5.0.md +127 -0
- package/releases/2.0.0.md +32 -0
- package/src/models/Model.js +110 -368
- package/src/models/ModelInstance.js +52 -10
- package/src/utils/buildQuery.js +72 -0
- package/src/utils/formatObject.js +6 -4
- package/src/utils/generateCondition.js +9 -6
- package/src/utils/security/safe.js +35 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Bug report
|
|
3
|
+
about: Create a report to help us improve
|
|
4
|
+
title: ''
|
|
5
|
+
labels: ''
|
|
6
|
+
assignees: lagie-marin
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Bug Description
|
|
11
|
+
|
|
12
|
+
## Steps to Reproduce
|
|
13
|
+
|
|
14
|
+
1. Call the method `...`
|
|
15
|
+
2. Pass the following options/parameters `...`
|
|
16
|
+
3. Execute the code
|
|
17
|
+
4. See the error
|
|
18
|
+
|
|
19
|
+
## Code Snippet / Logs
|
|
20
|
+
|
|
21
|
+
**JavaScript Code:**
|
|
22
|
+
|
|
23
|
+
```javascript
|
|
24
|
+
// Insert the JavaScript code that triggers the issue here
|
|
25
|
+
const result = await MyModel.find({ ... });
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Error Logs / Output
|
|
29
|
+
|
|
30
|
+
```txt
|
|
31
|
+
TypeError: ... is not a function
|
|
32
|
+
at ...
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Expected Behavior
|
|
36
|
+
|
|
37
|
+
Example: The `find()` method should return a native JavaScript Array of `ModelInstance` objects so that `.length` or indexation like `[0]` works out of the box when multiple rows are returned.
|
|
38
|
+
|
|
39
|
+
## Environment
|
|
40
|
+
|
|
41
|
+
- sql-connector version: vX.X.X
|
|
42
|
+
- Node.js version: vXX.XX.X
|
|
43
|
+
- Database (MySQL/MariaDB...): MySQL vX.X
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Feature request
|
|
3
|
+
about: Suggest an idea for this project
|
|
4
|
+
title: ''
|
|
5
|
+
labels: ''
|
|
6
|
+
assignees: lagie-marin
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Feature Type
|
|
11
|
+
- [ ] New method / API addition
|
|
12
|
+
- [ ] Performance improvement
|
|
13
|
+
- [ ] Architecture Refactoring (ORM / Data Mapping)
|
|
14
|
+
- [ ] Other:
|
|
15
|
+
|
|
16
|
+
## Problem Statement
|
|
17
|
+
*Example: Currently, the ORM wraps the raw driver payload `[rows, fields]` inside a single global `ModelInstance`. This forces developers to leak internal database structure by accessing nested arrays like `data[0][0]` when handling records, destroying proper encapsulation.*
|
|
18
|
+
|
|
19
|
+
## Proposed Solution
|
|
20
|
+
*Example: Refactor the `find()` method to map and split the query results. It should return a native Array where each row is mapped into its own independent `ModelInstance`. Implement dynamic data-mapping (via continuous references, Getters/Setters, or a Proxy) to allow direct property access (`job.status`) without cloning the memory footprint.*
|
|
21
|
+
|
|
22
|
+
## Desired Developer Experience (DX / Example)
|
|
23
|
+
```javascript
|
|
24
|
+
// Provide an example of how the ideal code should look after this feature:
|
|
25
|
+
const jobs = await InjectionJobs.find({ where: { status: "running" } });
|
|
26
|
+
|
|
27
|
+
console.log(jobs.length); // Native array length works
|
|
28
|
+
console.log(jobs[0].status); // Direct property access on the instance
|
|
29
|
+
await jobs[0].updateOne({ status: "success" }); // Instance methods remain fully bound
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
name: Publish Package
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
check_security:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
steps:
|
|
12
|
+
- name: Checkout GH repository
|
|
13
|
+
uses: actions/checkout@v6
|
|
14
|
+
- name: setup node
|
|
15
|
+
uses: actions/setup-node@v6
|
|
16
|
+
with:
|
|
17
|
+
node-version: 22
|
|
18
|
+
- name: Install deps
|
|
19
|
+
run: npm ci
|
|
20
|
+
- name: Audit deps
|
|
21
|
+
continue-on-error: false
|
|
22
|
+
run: |
|
|
23
|
+
npm audit
|
|
24
|
+
- name: Check security of module
|
|
25
|
+
continue-on-error: false
|
|
26
|
+
run: |
|
|
27
|
+
npm run security
|
|
28
|
+
publish:
|
|
29
|
+
needs: check_security
|
|
30
|
+
runs-on: ubuntu-latest
|
|
31
|
+
environment: sqlc
|
|
32
|
+
permissions:
|
|
33
|
+
contents: read
|
|
34
|
+
id-token: write
|
|
35
|
+
|
|
36
|
+
steps:
|
|
37
|
+
- uses: actions/checkout@v6
|
|
38
|
+
- uses: actions/setup-node@v6
|
|
39
|
+
with:
|
|
40
|
+
node-version: 22
|
|
41
|
+
registry-url: https://registry.npmjs.org
|
|
42
|
+
scope: "@mlagie"
|
|
43
|
+
- name: Update npm and Publish
|
|
44
|
+
run: |
|
|
45
|
+
npm install -g npm@latest
|
|
46
|
+
npm ci
|
|
47
|
+
npm publish --provenance --access public
|
package/README.md
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# sql-connector documentation
|
|
2
2
|
|
|
3
|
+
    
|
|
4
|
+

|
|
5
|
+
|
|
3
6
|
[Français](./docs/fr/README.md) | English
|
|
4
7
|
|
|
5
8
|
sql-connector helps manage MySQL connections, define table schemas, sync tables automatically, and work with database models through a small API.
|
|
@@ -32,41 +35,91 @@ await connect(config);
|
|
|
32
35
|
await logout();
|
|
33
36
|
```
|
|
34
37
|
|
|
38
|
+
### Example
|
|
39
|
+
|
|
40
|
+
```js
|
|
41
|
+
const { connect: dbConnect, client, Model } = require("@mlagie/sql-connector");
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
await dbConnect({
|
|
45
|
+
host: 'localhost',
|
|
46
|
+
port: 3306,
|
|
47
|
+
user: 'root',
|
|
48
|
+
password: 'password',
|
|
49
|
+
database: 'mydatabase',
|
|
50
|
+
connectionLimit: 2,
|
|
51
|
+
multipleStatements: true,
|
|
52
|
+
idleTimeout: 10000,
|
|
53
|
+
typeCast: true,
|
|
54
|
+
}).then(() => { Logger.client("- connected to the database") }).catch(error => {
|
|
55
|
+
console.error(error);
|
|
56
|
+
process.exit();
|
|
57
|
+
});
|
|
58
|
+
```
|
|
59
|
+
|
|
35
60
|
## Schema
|
|
36
61
|
|
|
37
62
|
`Schema` describes the structure of a table. Each field can use the following properties.
|
|
38
63
|
|
|
39
|
-
| Property
|
|
40
|
-
|
|
41
|
-
| type
|
|
42
|
-
| length
|
|
43
|
-
| required
|
|
44
|
-
| default
|
|
45
|
-
| unique
|
|
46
|
-
| auto_increment | `boolean`
|
|
47
|
-
| foreignKey
|
|
48
|
-
| enum
|
|
49
|
-
| primary_key
|
|
50
|
-
| customize
|
|
64
|
+
| Property | Type | Description |
|
|
65
|
+
|----------------|----------------------------------|------------------------|
|
|
66
|
+
| type | `SqlType` or `{ name: SqlType }` | SQL type for the field |
|
|
67
|
+
| length | `number` | Maximum length |
|
|
68
|
+
| required | `boolean` | Not null constraint |
|
|
69
|
+
| default | `any` | Default value |
|
|
70
|
+
| unique | `boolean` | Unique constraint |
|
|
71
|
+
| auto_increment | `boolean` | Auto increment |
|
|
72
|
+
| foreignKey | `string` | Foreign key reference |
|
|
73
|
+
| enum | `string[]` | Allowed values |
|
|
74
|
+
| primary_key | `boolean` | Primary key flag |
|
|
75
|
+
| customize | `string` | Extra SQL options |
|
|
76
|
+
|
|
77
|
+
### Example Schema creation & Model Creation
|
|
51
78
|
|
|
52
79
|
```javascript
|
|
80
|
+
const { Schema, Model, sqlTypeMap } = require("@mlagie/sql-connector");
|
|
81
|
+
|
|
53
82
|
const userSchema = new Schema({
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
83
|
+
id: {
|
|
84
|
+
type: Number,
|
|
85
|
+
auto_increment: true,
|
|
86
|
+
primary_key: true
|
|
87
|
+
},
|
|
88
|
+
group_uuid: {
|
|
89
|
+
type: String,
|
|
90
|
+
required: true,
|
|
91
|
+
primary_key: true,
|
|
92
|
+
length: 36
|
|
93
|
+
},
|
|
94
|
+
email: {
|
|
95
|
+
type: String,
|
|
96
|
+
length: 255,
|
|
97
|
+
unique: true,
|
|
98
|
+
required: true
|
|
99
|
+
},
|
|
100
|
+
status: {
|
|
101
|
+
type: String,
|
|
102
|
+
enum: ['active', 'inactive', 'pending'],
|
|
103
|
+
default: 'pending'
|
|
104
|
+
},
|
|
105
|
+
uuid: {
|
|
106
|
+
type: String,
|
|
107
|
+
required: true,
|
|
108
|
+
primary_key: true,
|
|
109
|
+
length: 36
|
|
110
|
+
},
|
|
111
|
+
my_uuid: {
|
|
112
|
+
type: String,
|
|
113
|
+
required: true,
|
|
114
|
+
length: 36
|
|
115
|
+
},
|
|
116
|
+
created_at: {
|
|
117
|
+
type: Date,
|
|
118
|
+
default: sqlTypeMap.CurrentTimestamp
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
module.exports = new Model("User", userSchema);
|
|
122
|
+
|
|
70
123
|
});
|
|
71
124
|
```
|
|
72
125
|
|
|
@@ -75,13 +128,9 @@ const userSchema = new Schema({
|
|
|
75
128
|
`Model.syncAllTables()` compares JS schemas with the database and applies only meaningful differences.
|
|
76
129
|
|
|
77
130
|
- New columns are added automatically.
|
|
78
|
-
- Removed columns are only dropped with `dangerousSync: true`.
|
|
79
|
-
- Column renames are supported through `oldName`.
|
|
80
|
-
- Orphan tables are backed up to a `backup_*.sql` file before deletion.
|
|
81
131
|
|
|
82
132
|
```javascript
|
|
83
133
|
await Model.syncAllTables();
|
|
84
|
-
await Model.syncAllTables({ dangerousSync: true });
|
|
85
134
|
```
|
|
86
135
|
|
|
87
136
|
Important: do not set both `primary_key: true` and `unique: true` on the same field. A primary key is already unique and not null.
|
|
@@ -92,25 +141,188 @@ Important: do not set both `primary_key: true` and `unique: true` on the same fi
|
|
|
92
141
|
|
|
93
142
|
Main methods:
|
|
94
143
|
|
|
95
|
-
- `save(data)`
|
|
96
|
-
- `
|
|
97
|
-
- `
|
|
98
|
-
- `
|
|
99
|
-
- `
|
|
100
|
-
- `
|
|
101
|
-
- `
|
|
102
|
-
- `dropTable()` drops the table
|
|
103
|
-
- `generate_uuid()` generates a unique UUID
|
|
104
|
-
- `Model.createAllTables()` creates tables in dependency order
|
|
144
|
+
- `save(data)` Inserts a row
|
|
145
|
+
- `find(options)` Retrieves entries from the table.
|
|
146
|
+
- `count(filter)` Counts rows
|
|
147
|
+
- `customRequest(custom)` Runs a custom SQL query
|
|
148
|
+
- `delete(filter)` Deletes a row
|
|
149
|
+
- `dropTable()` Drops the table
|
|
150
|
+
- `generate_uuid()` Generates a unique UUID
|
|
105
151
|
|
|
106
152
|
```javascript
|
|
107
153
|
const userModel = new Model('users', userSchema);
|
|
108
154
|
|
|
109
|
-
await Model.
|
|
155
|
+
await Model.syncAllTables();
|
|
110
156
|
await userModel.save({ email: 'user@example.com', status: 'active' });
|
|
111
157
|
|
|
112
|
-
const user = await userModel.
|
|
113
|
-
await
|
|
158
|
+
const user = await userModel.find({ where: { email: 'user@example.com' }});
|
|
159
|
+
await user[0].deleteOne();
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## save function
|
|
163
|
+
|
|
164
|
+
Saves data to the database table.
|
|
165
|
+
|
|
166
|
+
- **Parameters** `data` *(Object)* - The data to insert into the table.
|
|
167
|
+
- **Returns** `Promise<Object>` - A promise that resolves with the result of the insertion.
|
|
168
|
+
- **Throws** `Error` - Throws an error if the insert fails.
|
|
169
|
+
|
|
170
|
+
```js
|
|
171
|
+
const User = require("user");
|
|
172
|
+
|
|
173
|
+
async function createUser(email, stat) {
|
|
174
|
+
if (!email || !stat) {
|
|
175
|
+
console.error("Email & stat is required");
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
await User.save({ email: email, status: stat });
|
|
179
|
+
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## find function
|
|
184
|
+
|
|
185
|
+
Retrieves entries from the table.
|
|
186
|
+
|
|
187
|
+
- **Parameters** `options` *(Object)* - Query options
|
|
188
|
+
- **Parameters** `options.select` *(string[])* - Fields to be returned.
|
|
189
|
+
- **Parameters** `options.where` *(Object)* - Filters (key/value).
|
|
190
|
+
- **Parameters** `options.order` *(Array)* - Ex: [['points', 'DESC']]
|
|
191
|
+
- **Parameters** `options.limit` *(number)* - Limit of results.
|
|
192
|
+
- **Returns** `Promise<Array<ModelInstance>>`
|
|
193
|
+
|
|
194
|
+
### find Options
|
|
195
|
+
|
|
196
|
+
| Option | Type | Description | Example |
|
|
197
|
+
|------------|-----------------|----------------------------------------------------------|----------------------------------------------|
|
|
198
|
+
| `select` | Array | Fields or transformations to retrieve | `['date_day']` |
|
|
199
|
+
| `where` | Object / String | Filtering conditions | `{ project_id: 1 }` |
|
|
200
|
+
| `groupBy` | Array | Fields used to group results | `['period']` |
|
|
201
|
+
| `orderBy` | Array | Sorting rules | `[{ field: 'date_day', direction: 'DESC' }]` |
|
|
202
|
+
| `having` | String | HAVING clause for aggregated queries | `'SUM(total_runs) > 100'` |
|
|
203
|
+
| `limit` | Number | Limits the number of results | `100` |
|
|
204
|
+
|
|
205
|
+
## Example find
|
|
206
|
+
|
|
207
|
+
```js
|
|
208
|
+
const User = require("user");
|
|
209
|
+
|
|
210
|
+
User.find({
|
|
211
|
+
select: [
|
|
212
|
+
{ dateFormat: ['date_day', '%Y-%m'], as: 'period' },
|
|
213
|
+
{ sum: 'error' },
|
|
214
|
+
{ sum: 'reload' },
|
|
215
|
+
],
|
|
216
|
+
groupBy: ['period'],
|
|
217
|
+
orderBy: [{ field: 'period', direction: 'ASC' }],
|
|
218
|
+
limit: 10
|
|
219
|
+
});
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
```js
|
|
223
|
+
const User = require("user");
|
|
224
|
+
|
|
225
|
+
User.find({
|
|
226
|
+
select: [
|
|
227
|
+
"email"
|
|
228
|
+
],
|
|
229
|
+
where: {
|
|
230
|
+
id: 1
|
|
231
|
+
}
|
|
232
|
+
})
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
## count function
|
|
236
|
+
|
|
237
|
+
Counts the number of records matching the given filter.
|
|
238
|
+
|
|
239
|
+
- **Parameters** `filter` *(Object)* The filter criteria for the query. Should be an object where keys are column names and values are the values to filter by.
|
|
240
|
+
- **Returns** `Promise<ModelInstance|number>` - A promise that resolves to a `ModelInstance` if a record is found, or `0` if no records match the filter.
|
|
241
|
+
|
|
242
|
+
## Example count
|
|
243
|
+
|
|
244
|
+
```js
|
|
245
|
+
const User = require("user");
|
|
246
|
+
|
|
247
|
+
User.count({
|
|
248
|
+
id: id
|
|
249
|
+
})
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
## customRequest function
|
|
253
|
+
|
|
254
|
+
The customRequest function allows you to execute SQL queries that are not supported by sql-connector; this could be in queries where the keywords are not yet implemented.
|
|
255
|
+
|
|
256
|
+
- **Parameters** `custom` *(string)* The custom SQL_request query to execute.
|
|
257
|
+
- **Returns** `Promise<void>` A promise that resolves when the query is executed.
|
|
258
|
+
- **Throws** `Error` Throws an error if query execution fails.
|
|
259
|
+
|
|
260
|
+
## Example customRequest
|
|
261
|
+
|
|
262
|
+
```js
|
|
263
|
+
const User = require("user");
|
|
264
|
+
|
|
265
|
+
User.customRequest("SELECT id, email, status
|
|
266
|
+
FROM users
|
|
267
|
+
WHERE status IN ('active', 'pending')
|
|
268
|
+
AND email LIKE '%gmail.com';")
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
## delete function
|
|
272
|
+
|
|
273
|
+
Deletes an entry from the SQL table that matches the provided filter.
|
|
274
|
+
|
|
275
|
+
- **Parameters** `filter` *(Object)* An object representing the filter conditions for deletion.
|
|
276
|
+
- **Returns** `Promise<number>` A promise that resolves to 0 if no rows were deleted, * or to a ModelInstance representing the deleted row.
|
|
277
|
+
- **Throws** `Error` Throws an error if the SQL query fails.
|
|
278
|
+
|
|
279
|
+
## Example delete
|
|
280
|
+
|
|
281
|
+
```js
|
|
282
|
+
const User = require("user");
|
|
283
|
+
|
|
284
|
+
User.delete({
|
|
285
|
+
email: my@gmail.com
|
|
286
|
+
})
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
## dropTable function
|
|
290
|
+
|
|
291
|
+
Asynchronously drops a table if it exists in the database.
|
|
292
|
+
|
|
293
|
+
This function constructs a SQL_request query to drop a table with the name specified by the `this.name` property. It then executes the query using a promise-based approach.
|
|
294
|
+
If the query is successful, the result is logged to the console.
|
|
295
|
+
If an error occurs during the execution of the query, an error message is logged.
|
|
296
|
+
|
|
297
|
+
- **Returns** `Promise<void>` A promise that resolves when the query execution is complete.
|
|
298
|
+
|
|
299
|
+
## Example dropTable
|
|
300
|
+
|
|
301
|
+
```js
|
|
302
|
+
const User = require("user");
|
|
303
|
+
|
|
304
|
+
User.dropTable();
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
## generate_uuid function
|
|
308
|
+
|
|
309
|
+
Generates a unique UUID for the current model.
|
|
310
|
+
This function generates a UUID using the SQL_request `UUID()` function and checks if the generated UUID already exists in the database for the current model. If the UUID is unique, it is returned.
|
|
311
|
+
Otherwise, the function resolves to `null`.
|
|
312
|
+
|
|
313
|
+
- **Parameters** `string` var_uuid By default, it is set to uuid.
|
|
314
|
+
- **Returns** `Promise<string|null>` A promise that resolves to a unique UUID string if successful, or `null` if an error occurs or the UUID is not unique.
|
|
315
|
+
- **Throws** `Error` If there is an error executing the SQL_request query.
|
|
316
|
+
|
|
317
|
+
## Example generate_uuid
|
|
318
|
+
|
|
319
|
+
```js
|
|
320
|
+
const User = require("user");
|
|
321
|
+
|
|
322
|
+
const uuid = await User.generate_uuid();
|
|
323
|
+
const my_uuid = await User.generate_uuid("my_uuid");
|
|
324
|
+
|
|
325
|
+
await User.save{ email: "user@example.com", status: "active", uuid: uuid, my_uuid: my_uuid }
|
|
114
326
|
```
|
|
115
327
|
|
|
116
328
|
## Model instances
|
|
@@ -122,8 +334,8 @@ await userModel.delete({ email: 'user@example.com' });
|
|
|
122
334
|
- `deleteOne()` deletes the instance row
|
|
123
335
|
- `customRequest(custom)` runs a custom query
|
|
124
336
|
|
|
125
|
-
```
|
|
126
|
-
const userInstance =
|
|
337
|
+
```js
|
|
338
|
+
const userInstance = await find({ select: "users", where: { email: 'user@example.com' }})[0];
|
|
127
339
|
|
|
128
340
|
await userInstance.updateOne({ status: 'inactive' });
|
|
129
341
|
await userInstance.deleteOne();
|
|
@@ -155,4 +367,4 @@ module.exports = client => {
|
|
|
155
367
|
|
|
156
368
|
## Summary
|
|
157
369
|
|
|
158
|
-
sql-connector provides a small layer to connect to MySQL, describe schemas, synchronize tables, and manipulate data with typed models.
|
|
370
|
+
sql-connector provides a small layer to connect to MySQL, describe schemas, synchronize tables, and manipulate data with typed models.
|