meadow 2.0.17 → 2.0.18

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.
@@ -0,0 +1,296 @@
1
+ # MSSQL Provider
2
+
3
+ > Enterprise-grade SQL Server integration with prepared statements and automatic type mapping
4
+
5
+ The MSSQL provider connects Meadow to Microsoft SQL Server via the [mssql](https://github.com/tediousjs/node-mssql) library. It uses prepared statements for every operation, automatically maps FoxHound parameter types to MSSQL types, and handles identity column insertion with `SCOPE_IDENTITY()`.
6
+
7
+ ## Setup
8
+
9
+ ### Install Dependencies
10
+
11
+ ```bash
12
+ npm install meadow meadow-connection-mssql
13
+ ```
14
+
15
+ ### Register the Connection
16
+
17
+ ```javascript
18
+ const libFable = require('fable').new(
19
+ {
20
+ MSSQL:
21
+ {
22
+ server: 'localhost',
23
+ port: 1433,
24
+ user: 'sa',
25
+ password: 'YourPassword',
26
+ database: 'myapp'
27
+ }
28
+ });
29
+
30
+ const libMeadowConnectionMSSQL = require('meadow-connection-mssql');
31
+
32
+ // Register the connection service
33
+ libFable.serviceManager.addServiceType('MeadowMSSQLProvider', libMeadowConnectionMSSQL);
34
+ const tmpConnection = libFable.serviceManager.instantiateServiceProvider('MeadowMSSQLProvider');
35
+
36
+ // Connect asynchronously (required for MSSQL)
37
+ tmpConnection.connectAsync(
38
+ (pError) =>
39
+ {
40
+ if (pError)
41
+ {
42
+ return console.error('MSSQL connection failed:', pError);
43
+ }
44
+ console.log('Connected to SQL Server');
45
+ });
46
+ ```
47
+
48
+ ### Create a Meadow DAL
49
+
50
+ ```javascript
51
+ const libMeadow = require('meadow');
52
+
53
+ const meadow = libMeadow.new(libFable, 'Book')
54
+ .setProvider('MSSQL')
55
+ .setDefaultIdentifier('IDBook')
56
+ .setSchema([
57
+ { Column: 'IDBook', Type: 'AutoIdentity' },
58
+ { Column: 'GUIDBook', Type: 'AutoGUID' },
59
+ { Column: 'Title', Type: 'String', Size: '255' },
60
+ { Column: 'Author', Type: 'String', Size: '128' },
61
+ { Column: 'Price', Type: 'Decimal', Size: '12,2' },
62
+ { Column: 'CreateDate', Type: 'CreateDate' },
63
+ { Column: 'CreatingIDUser', Type: 'CreateIDUser' },
64
+ { Column: 'UpdateDate', Type: 'UpdateDate' },
65
+ { Column: 'UpdatingIDUser', Type: 'UpdateIDUser' },
66
+ { Column: 'DeleteDate', Type: 'DeleteDate' },
67
+ { Column: 'DeletingIDUser', Type: 'DeleteIDUser' },
68
+ { Column: 'Deleted', Type: 'Deleted' }
69
+ ]);
70
+ ```
71
+
72
+ ## Connection Management
73
+
74
+ ### Async Connection Model
75
+
76
+ Unlike MySQL, MSSQL connections are fully asynchronous. The connection module provides:
77
+
78
+ | Method | Description |
79
+ |--------|-------------|
80
+ | `connectAsync(fCallback)` | Async connection with callback on completion |
81
+ | `connect()` | Sync wrapper (warns about race conditions, delegates to `connectAsync`) |
82
+
83
+ ### Connection Pool Settings
84
+
85
+ | Setting | Type | Default | Description |
86
+ |---------|------|---------|-------------|
87
+ | `MSSQL.server` | string | — | SQL Server hostname |
88
+ | `MSSQL.port` | number | `1433` | SQL Server port |
89
+ | `MSSQL.user` | string | — | Database user |
90
+ | `MSSQL.password` | string | — | Database password |
91
+ | `MSSQL.database` | string | — | Database name |
92
+
93
+ ### Pool Configuration (Internal Defaults)
94
+
95
+ | Setting | Default | Description |
96
+ |---------|---------|-------------|
97
+ | `pool.max` | `10` | Maximum pool connections |
98
+ | `pool.min` | `0` | Minimum pool connections |
99
+ | `pool.idleTimeoutMillis` | `30000` | Idle connection timeout |
100
+ | `requestTimeout` | `80000` | Query request timeout (ms) |
101
+ | `connectionTimeout` | `80000` | Connection timeout (ms) |
102
+ | `trustServerCertificate` | `true` | Accept self-signed certificates |
103
+
104
+ ## Prepared Statements
105
+
106
+ The MSSQL provider uses prepared statements for **every** CRUD operation. This provides:
107
+
108
+ - **Security** — Parameters are bound separately from SQL, preventing injection
109
+ - **Performance** — SQL Server can cache execution plans
110
+ - **Type Safety** — Parameters are explicitly typed
111
+
112
+ ### Execution Lifecycle
113
+
114
+ Each operation follows this lifecycle:
115
+
116
+ ```
117
+ 1. Build SQL via FoxHound
118
+ 2. Create PreparedStatement from connection pool
119
+ 3. Add typed parameters via input(name, type)
120
+ 4. Prepare the statement
121
+ 5. Execute with parameter values
122
+ 6. Unprepare the statement
123
+ 7. Return results via callback
124
+ ```
125
+
126
+ ### Type Mapping
127
+
128
+ FoxHound parameter types are automatically mapped to MSSQL types:
129
+
130
+ | FoxHound Type | MSSQL Type | Notes |
131
+ |---------------|------------|-------|
132
+ | `String` / `VarChar` | `MSSQL.VarChar(Max)` | Variable-length strings |
133
+ | `Text` | `MSSQL.VarChar(Max)` | Long text fields |
134
+ | `Decimal` | `MSSQL.Decimal(digits, decimalDigits)` | Precision from schema `Size` |
135
+ | Other types | `MSSQL[typeName]` | Direct lookup on MSSQL type object |
136
+
137
+ For Decimal columns, the precision and scale are read from the schema's `Size` property (e.g., `'12,2'` becomes `MSSQL.Decimal(12, 2)`).
138
+
139
+ ## CRUD Operations
140
+
141
+ ### Create
142
+
143
+ Executes an INSERT with `SCOPE_IDENTITY()` to capture the generated identity.
144
+
145
+ ```javascript
146
+ meadow.doCreate(
147
+ meadow.query.addRecord({ Title: 'Dune', Author: 'Frank Herbert' }),
148
+ (pError, pCreateQuery, pReadQuery, pRecord) =>
149
+ {
150
+ console.log('New ID:', pRecord.IDBook);
151
+ });
152
+ ```
153
+
154
+ **Internally:**
155
+ - Builds query: `pQuery.setDialect('MSSQL').buildCreateQuery()`
156
+ - Appends: `SELECT SCOPE_IDENTITY() AS value;`
157
+ - Extracts identity: `recordset[0].value`
158
+
159
+ ### Identity Insert
160
+
161
+ For scenarios where you need to insert explicit identity values (e.g., data migration):
162
+
163
+ ```javascript
164
+ const tmpQuery = meadow.query
165
+ .addRecord({ IDBook: 500, Title: 'Imported Book' });
166
+ tmpQuery.parameters.AllowIdentityInsert = true;
167
+
168
+ meadow.doCreate(tmpQuery,
169
+ (pError, pCreateQuery, pReadQuery, pRecord) =>
170
+ {
171
+ console.log('Inserted with explicit ID:', pRecord.IDBook);
172
+ });
173
+ ```
174
+
175
+ When `AllowIdentityInsert` is `true`, the provider wraps the INSERT with:
176
+
177
+ ```sql
178
+ SET IDENTITY_INSERT [Book] ON;
179
+ INSERT INTO Book (...) VALUES (...); SELECT SCOPE_IDENTITY() AS value;
180
+ SET IDENTITY_INSERT [Book] OFF;
181
+ ```
182
+
183
+ ### Read
184
+
185
+ Executes a SELECT and returns result rows from the recordset.
186
+
187
+ ```javascript
188
+ meadow.doRead(
189
+ meadow.query.addFilter('IDBook', 42),
190
+ (pError, pQuery, pRecord) =>
191
+ {
192
+ console.log('Title:', pRecord.Title);
193
+ });
194
+
195
+ meadow.doReads(
196
+ meadow.query.setCap(25).setBegin(0),
197
+ (pError, pQuery, pRecords) =>
198
+ {
199
+ pRecords.forEach((pBook) => console.log(pBook.Title));
200
+ });
201
+ ```
202
+
203
+ **Internally:**
204
+ - Builds query: `pQuery.setDialect('MSSQL').buildReadQuery()`
205
+ - Returns: `recordset` array from prepared statement execution
206
+
207
+ ### Update
208
+
209
+ Executes an UPDATE with prepared statement parameters.
210
+
211
+ ```javascript
212
+ meadow.doUpdate(
213
+ meadow.query
214
+ .addFilter('IDBook', 42)
215
+ .addRecord({ Title: 'Updated Title' }),
216
+ (pError, pUpdateQuery, pReadQuery, pRecord) =>
217
+ {
218
+ console.log('Updated:', pRecord.Title);
219
+ });
220
+ ```
221
+
222
+ ### Delete
223
+
224
+ Executes a soft delete and returns the affected row count.
225
+
226
+ ```javascript
227
+ meadow.doDelete(
228
+ meadow.query.addFilter('IDBook', 42),
229
+ (pError, pQuery, pResult) =>
230
+ {
231
+ console.log('Deleted rows:', pResult);
232
+ });
233
+ ```
234
+
235
+ **Internally:**
236
+ - Extracts affected rows: `rowsAffected[0]`
237
+
238
+ ### Undelete
239
+
240
+ Reverses a soft delete.
241
+
242
+ ```javascript
243
+ meadow.doUndelete(
244
+ meadow.query.addFilter('IDBook', 42),
245
+ (pError, pQuery, pResult) =>
246
+ {
247
+ console.log('Restored rows:', pResult);
248
+ });
249
+ ```
250
+
251
+ ### Count
252
+
253
+ Returns the matching record count.
254
+
255
+ ```javascript
256
+ meadow.doCount(
257
+ meadow.query.addFilter('Author', 'Frank Herbert', '='),
258
+ (pError, pQuery, pCount) =>
259
+ {
260
+ console.log('Books by Herbert:', pCount);
261
+ });
262
+ ```
263
+
264
+ **Internally:**
265
+ - Extracts count: `recordset[0].Row_Count`
266
+ - Falls back to `-1` if extraction fails
267
+
268
+ ## Error Handling
269
+
270
+ The MSSQL provider has detailed error handling at each stage:
271
+
272
+ | Stage | Error Level | Message Pattern |
273
+ |-------|-------------|-----------------|
274
+ | Preparation | `log.error` | `"Create Error preparing prepared statement"` |
275
+ | Execution | `result.error` | Error stored on query result |
276
+ | Unprepare | `log.error` | `"Create Error unpreparing prepared statement"` |
277
+ | Marshalling | `log.warn` | Warning with query body for debugging |
278
+
279
+ ## Docker Development
280
+
281
+ For local SQL Server development:
282
+
283
+ ```bash
284
+ docker run -d \
285
+ --name meadow-mssql \
286
+ -e ACCEPT_EULA=Y \
287
+ -e SA_PASSWORD=YourStrong!Password \
288
+ -p 1433:1433 \
289
+ mcr.microsoft.com/mssql/server:2022-latest
290
+ ```
291
+
292
+ ## Related Documentation
293
+
294
+ - [Providers Overview](providers/README.md) — Comparison of all providers
295
+ - [MySQL Provider](providers/mysql.md) — MySQL/MariaDB alternative
296
+ - [meadow-connection-mssql](https://github.com/stevenvelozo/meadow-connection-mssql) — Connection module source
@@ -0,0 +1,260 @@
1
+ # MySQL Provider
2
+
3
+ > Production-ready MySQL/MariaDB integration with connection pooling and named placeholders
4
+
5
+ The MySQL provider connects Meadow to MySQL and MariaDB databases via the [mysql2](https://github.com/sidorares/node-mysql2) library. It uses connection pooling for efficient resource management and named placeholders for safe parameter binding.
6
+
7
+ ## Setup
8
+
9
+ ### Install Dependencies
10
+
11
+ ```bash
12
+ npm install meadow meadow-connection-mysql
13
+ ```
14
+
15
+ ### Register the Connection
16
+
17
+ ```javascript
18
+ const libFable = require('fable').new(
19
+ {
20
+ MySQL:
21
+ {
22
+ Server: 'localhost',
23
+ Port: 3306,
24
+ User: 'root',
25
+ Password: '',
26
+ Database: 'myapp',
27
+ ConnectionPoolLimit: 20
28
+ }
29
+ });
30
+
31
+ const libMeadowConnectionMySQL = require('meadow-connection-mysql');
32
+
33
+ // Register and instantiate the connection service
34
+ libFable.serviceManager.addServiceType('MeadowMySQLProvider', libMeadowConnectionMySQL);
35
+ libFable.serviceManager.instantiateServiceProvider('MeadowMySQLProvider',
36
+ { MeadowConnectionMySQLAutoConnect: true });
37
+ ```
38
+
39
+ ### Create a Meadow DAL
40
+
41
+ ```javascript
42
+ const libMeadow = require('meadow');
43
+
44
+ const meadow = libMeadow.new(libFable, 'Book')
45
+ .setProvider('MySQL')
46
+ .setDefaultIdentifier('IDBook')
47
+ .setSchema([
48
+ { Column: 'IDBook', Type: 'AutoIdentity' },
49
+ { Column: 'GUIDBook', Type: 'AutoGUID' },
50
+ { Column: 'Title', Type: 'String', Size: '255' },
51
+ { Column: 'Author', Type: 'String', Size: '128' },
52
+ { Column: 'CreateDate', Type: 'CreateDate' },
53
+ { Column: 'CreatingIDUser', Type: 'CreateIDUser' },
54
+ { Column: 'UpdateDate', Type: 'UpdateDate' },
55
+ { Column: 'UpdatingIDUser', Type: 'UpdateIDUser' },
56
+ { Column: 'DeleteDate', Type: 'DeleteDate' },
57
+ { Column: 'DeletingIDUser', Type: 'DeleteIDUser' },
58
+ { Column: 'Deleted', Type: 'Deleted' }
59
+ ]);
60
+ ```
61
+
62
+ ## Connection Management
63
+
64
+ ### Connection Pool
65
+
66
+ The MySQL provider uses a connection pool managed by `meadow-connection-mysql`. Each CRUD operation:
67
+
68
+ 1. Acquires a connection from the pool via `pool.getConnection()`
69
+ 2. Executes the generated SQL query
70
+ 3. Releases the connection back to the pool
71
+
72
+ This ensures efficient reuse of database connections across concurrent requests.
73
+
74
+ ### Pool Access
75
+
76
+ The provider looks for the connection pool in two locations (for backward compatibility):
77
+
78
+ | Property | Description |
79
+ |----------|-------------|
80
+ | `fable.MeadowMySQLProvider.pool` | New-style: registered Fable service |
81
+ | `fable.MeadowMySQLConnectionPool` | Legacy: directly assigned pool |
82
+
83
+ ### Connection Configuration
84
+
85
+ | Setting | Type | Default | Description |
86
+ |---------|------|---------|-------------|
87
+ | `MySQL.Server` | string | — | Database server hostname |
88
+ | `MySQL.Port` | number | `3306` | Database server port |
89
+ | `MySQL.User` | string | — | Database user |
90
+ | `MySQL.Password` | string | — | Database password |
91
+ | `MySQL.Database` | string | — | Database name |
92
+ | `MySQL.ConnectionPoolLimit` | number | `20` | Maximum pool connections |
93
+
94
+ ### Named Placeholders
95
+
96
+ The MySQL provider enables `namedPlaceholders: true` on the connection pool, which allows FoxHound to generate queries with named parameter binding:
97
+
98
+ ```sql
99
+ -- Generated by FoxHound with named placeholders:
100
+ SELECT IDBook, Title, Author FROM Book WHERE IDBook = :IDBook AND Deleted = 0
101
+ ```
102
+
103
+ ## CRUD Operations
104
+
105
+ ### Create
106
+
107
+ Executes an INSERT statement and returns the auto-generated identity value.
108
+
109
+ ```javascript
110
+ meadow.doCreate(
111
+ meadow.query.addRecord({ Title: 'Dune', Author: 'Frank Herbert' }),
112
+ (pError, pCreateQuery, pReadQuery, pRecord) =>
113
+ {
114
+ console.log('New ID:', pRecord.IDBook);
115
+ });
116
+ ```
117
+
118
+ **Internally:**
119
+ - Builds query: `pQuery.setDialect('MySQL').buildCreateQuery()`
120
+ - Extracts identity: `result.insertId`
121
+ - If `insertId` extraction fails, logs a warning with the query body for debugging
122
+
123
+ ### Read
124
+
125
+ Executes a SELECT statement and returns the result rows.
126
+
127
+ ```javascript
128
+ // Single record
129
+ meadow.doRead(
130
+ meadow.query.addFilter('IDBook', 42),
131
+ (pError, pQuery, pRecord) =>
132
+ {
133
+ console.log('Title:', pRecord.Title);
134
+ });
135
+
136
+ // Multiple records
137
+ meadow.doReads(
138
+ meadow.query.setCap(25).setBegin(0).addSort({ Column: 'Title', Direction: 'ASC' }),
139
+ (pError, pQuery, pRecords) =>
140
+ {
141
+ pRecords.forEach((pBook) => console.log(pBook.Title));
142
+ });
143
+ ```
144
+
145
+ **Internally:**
146
+ - Builds query: `pQuery.setDialect('MySQL').buildReadQuery()`
147
+ - Returns full result array
148
+
149
+ ### Update
150
+
151
+ Executes an UPDATE statement.
152
+
153
+ ```javascript
154
+ meadow.doUpdate(
155
+ meadow.query
156
+ .addFilter('IDBook', 42)
157
+ .addRecord({ Title: 'Updated Title' }),
158
+ (pError, pUpdateQuery, pReadQuery, pRecord) =>
159
+ {
160
+ console.log('Updated:', pRecord.Title);
161
+ });
162
+ ```
163
+
164
+ **Internally:**
165
+ - Builds query: `pQuery.setDialect('MySQL').buildUpdateQuery()`
166
+
167
+ ### Delete
168
+
169
+ Executes a soft delete (UPDATE setting Deleted flag) and returns the affected row count.
170
+
171
+ ```javascript
172
+ meadow.doDelete(
173
+ meadow.query.addFilter('IDBook', 42),
174
+ (pError, pQuery, pResult) =>
175
+ {
176
+ console.log('Deleted rows:', pResult);
177
+ });
178
+ ```
179
+
180
+ **Internally:**
181
+ - Builds query: `pQuery.setDialect('MySQL').buildDeleteQuery()`
182
+ - Extracts affected rows: `result.affectedRows`
183
+
184
+ ### Undelete
185
+
186
+ Reverses a soft delete and returns the affected row count.
187
+
188
+ ```javascript
189
+ meadow.doUndelete(
190
+ meadow.query.addFilter('IDBook', 42),
191
+ (pError, pQuery, pResult) =>
192
+ {
193
+ console.log('Restored rows:', pResult);
194
+ });
195
+ ```
196
+
197
+ **Internally:**
198
+ - Builds query: `pQuery.setDialect('MySQL').buildUndeleteQuery()`
199
+ - Extracts affected rows: `result.affectedRows`
200
+
201
+ ### Count
202
+
203
+ Executes a COUNT query and returns the integer count.
204
+
205
+ ```javascript
206
+ meadow.doCount(
207
+ meadow.query,
208
+ (pError, pQuery, pCount) =>
209
+ {
210
+ console.log('Total books:', pCount);
211
+ });
212
+ ```
213
+
214
+ **Internally:**
215
+ - Builds query: `pQuery.setDialect('MySQL').buildCountQuery()`
216
+ - Extracts count: `result[0].RowCount`
217
+
218
+ ## Query Logging
219
+
220
+ The MySQL provider supports query logging controlled by the `GlobalLogLevel` Fable setting:
221
+
222
+ | Level | Behavior |
223
+ |-------|----------|
224
+ | `0` (default) | No query logging |
225
+ | `> 0` | Logs trace-level messages for each query executed |
226
+
227
+ ```javascript
228
+ const libFable = require('fable').new(
229
+ {
230
+ GlobalLogLevel: 1,
231
+ MySQL: { /* ... */ }
232
+ });
233
+ ```
234
+
235
+ ## Error Handling
236
+
237
+ All operations follow the same error handling pattern:
238
+
239
+ - Database errors are stored in `pQuery.parameters.result.error`
240
+ - If the identity/rowcount extraction fails after a successful query, a warning is logged with the original query body
241
+ - Connection errors bubble up through the callback as `pError`
242
+
243
+ ## Docker Development
244
+
245
+ For local MySQL development:
246
+
247
+ ```bash
248
+ docker run -d \
249
+ --name meadow-mysql \
250
+ -e MYSQL_ROOT_PASSWORD=password \
251
+ -e MYSQL_DATABASE=myapp \
252
+ -p 3306:3306 \
253
+ mysql:8
254
+ ```
255
+
256
+ ## Related Documentation
257
+
258
+ - [Providers Overview](providers/README.md) — Comparison of all providers
259
+ - [MSSQL Provider](providers/mssql.md) — Microsoft SQL Server alternative
260
+ - [meadow-connection-mysql](https://github.com/stevenvelozo/meadow-connection-mysql) — Connection module source