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,350 @@
1
+ # Read
2
+
3
+ > Retrieve single or multiple records from the database
4
+
5
+ Meadow provides two read methods: `doRead` for fetching a single record and `doReads` for fetching multiple records with pagination, filtering, and performance profiling.
6
+
7
+ ## doRead - Single Record
8
+
9
+ ### Method Signature
10
+
11
+ ```javascript
12
+ meadow.doRead(pQuery, fCallBack)
13
+ ```
14
+
15
+ ### Callback
16
+
17
+ ```javascript
18
+ fCallBack(pError, pQuery, pRecord)
19
+ ```
20
+
21
+ | Parameter | Type | Description |
22
+ |-----------|------|-------------|
23
+ | `pError` | object/null | Error object if the operation failed, null on success |
24
+ | `pQuery` | object | The query that was executed |
25
+ | `pRecord` | object/undefined | The marshaled record, or `undefined` if not found |
26
+
27
+ ### Basic Usage
28
+
29
+ ```javascript
30
+ // Read a single book by ID
31
+ meadow.doRead(meadow.query.addFilter('IDBook', 42),
32
+ (pError, pQuery, pRecord) =>
33
+ {
34
+ if (pError)
35
+ {
36
+ return console.log('Read error:', pError);
37
+ }
38
+ if (!pRecord)
39
+ {
40
+ return console.log('Book not found');
41
+ }
42
+ console.log('Found:', pRecord.Title, 'by', pRecord.Author);
43
+ });
44
+ ```
45
+
46
+ ### Reading by GUID
47
+
48
+ ```javascript
49
+ meadow.doRead(meadow.query.addFilter('GUIDBook', 'a1b2c3d4-e5f6-7890-abcd-ef1234567890'),
50
+ (pError, pQuery, pRecord) =>
51
+ {
52
+ if (pRecord)
53
+ {
54
+ console.log('Found book:', pRecord.Title);
55
+ }
56
+ });
57
+ ```
58
+
59
+ ### Reading with Multiple Filters
60
+
61
+ ```javascript
62
+ const tmpQuery = meadow.query
63
+ .addFilter('Author', 'Asimov')
64
+ .addFilter('InPrint', 1);
65
+
66
+ meadow.doRead(tmpQuery,
67
+ (pError, pQuery, pRecord) =>
68
+ {
69
+ // Returns the first matching record
70
+ if (pRecord)
71
+ {
72
+ console.log('Found:', pRecord.Title);
73
+ }
74
+ });
75
+ ```
76
+
77
+ ### How It Works
78
+
79
+ ```
80
+ 1. Build Read Query
81
+ └── Apply filters, set dialect, generate SELECT SQL
82
+ 2. Execute via Provider
83
+ └── Run query against database
84
+ 3. Marshal Record
85
+ └── Convert DB row to plain JavaScript object using schema defaults
86
+ ```
87
+
88
+ The read operation returns only the first matching record. If no record matches, the callback receives `undefined` as the record parameter (not an error).
89
+
90
+ ---
91
+
92
+ ## doReads - Multiple Records
93
+
94
+ ### Method Signature
95
+
96
+ ```javascript
97
+ meadow.doReads(pQuery, fCallBack)
98
+ ```
99
+
100
+ ### Callback
101
+
102
+ ```javascript
103
+ fCallBack(pError, pQuery, pRecords)
104
+ ```
105
+
106
+ | Parameter | Type | Description |
107
+ |-----------|------|-------------|
108
+ | `pError` | object/null | Error object if the operation failed, null on success |
109
+ | `pQuery` | object | The query that was executed |
110
+ | `pRecords` | array | Array of marshaled records (empty array if none found) |
111
+
112
+ ### Basic Usage
113
+
114
+ ```javascript
115
+ // Read all books (up to default cap)
116
+ meadow.doReads(meadow.query,
117
+ (pError, pQuery, pRecords) =>
118
+ {
119
+ if (pError)
120
+ {
121
+ return console.log('Read error:', pError);
122
+ }
123
+ console.log('Found', pRecords.length, 'books');
124
+ pRecords.forEach((pBook) =>
125
+ {
126
+ console.log('-', pBook.Title);
127
+ });
128
+ });
129
+ ```
130
+
131
+ ### Pagination
132
+
133
+ ```javascript
134
+ // Page 1: first 25 records
135
+ meadow.doReads(meadow.query.setCap(25).setBegin(0),
136
+ (pError, pQuery, pRecords) =>
137
+ {
138
+ console.log('Page 1:', pRecords.length, 'records');
139
+ });
140
+
141
+ // Page 2: next 25 records
142
+ meadow.doReads(meadow.query.setCap(25).setBegin(25),
143
+ (pError, pQuery, pRecords) =>
144
+ {
145
+ console.log('Page 2:', pRecords.length, 'records');
146
+ });
147
+ ```
148
+
149
+ ### Filtering
150
+
151
+ ```javascript
152
+ // Read books by a specific author
153
+ meadow.doReads(meadow.query.addFilter('Author', 'Philip K. Dick'),
154
+ (pError, pQuery, pRecords) =>
155
+ {
156
+ console.log('Found', pRecords.length, 'books by PKD');
157
+ });
158
+
159
+ // Read with multiple filters
160
+ const tmpQuery = meadow.query
161
+ .addFilter('Author', 'Asimov')
162
+ .addFilter('PublishYear', 1950, '>')
163
+ .setCap(10);
164
+
165
+ meadow.doReads(tmpQuery,
166
+ (pError, pQuery, pRecords) =>
167
+ {
168
+ console.log('Found', pRecords.length, 'post-1950 Asimov books');
169
+ });
170
+ ```
171
+
172
+ ### Sorting
173
+
174
+ ```javascript
175
+ // Read books sorted by title
176
+ const tmpQuery = meadow.query
177
+ .addSort({ Column: 'Title', Direction: 'ASC' })
178
+ .setCap(50);
179
+
180
+ meadow.doReads(tmpQuery,
181
+ (pError, pQuery, pRecords) =>
182
+ {
183
+ pRecords.forEach((pBook) =>
184
+ {
185
+ console.log(pBook.Title);
186
+ });
187
+ });
188
+ ```
189
+
190
+ ### Column Selection
191
+
192
+ ```javascript
193
+ // Only retrieve specific columns for efficiency
194
+ const tmpQuery = meadow.query
195
+ .setDataElements(['IDBook', 'Title', 'Author'])
196
+ .setCap(100);
197
+
198
+ meadow.doReads(tmpQuery,
199
+ (pError, pQuery, pRecords) =>
200
+ {
201
+ // pRecords still contain all schema default fields,
202
+ // but only the selected columns will have DB values
203
+ });
204
+ ```
205
+
206
+ ### Distinct Queries
207
+
208
+ ```javascript
209
+ // Get unique author/publisher combinations
210
+ const tmpQuery = meadow.query
211
+ .setDistinct(true)
212
+ .setDataElements(['Author', 'Publisher']);
213
+
214
+ meadow.doReads(tmpQuery,
215
+ (pError, pQuery, pRecords) =>
216
+ {
217
+ pRecords.forEach((pRow) =>
218
+ {
219
+ console.log(pRow.Author, '-', pRow.Publisher);
220
+ });
221
+ });
222
+ ```
223
+
224
+ ### How It Works
225
+
226
+ ```
227
+ 1. Build Read Query
228
+ └── Apply filters, pagination, sorting, set dialect, generate SELECT SQL
229
+ 2. Execute via Provider
230
+ └── Run query against database, start performance timer
231
+ 3. Marshal Each Record
232
+ └── Convert each DB row to plain JavaScript object
233
+ 4. Performance Check
234
+ └── Log warning if query exceeded threshold (default 200ms)
235
+ ```
236
+
237
+ ---
238
+
239
+ ## Soft Delete Filtering
240
+
241
+ By default, both `doRead` and `doReads` automatically exclude soft-deleted records (where `Deleted = 1`). This filter is applied at the SQL dialect level.
242
+
243
+ ```javascript
244
+ // Default: only active records
245
+ meadow.doReads(meadow.query,
246
+ (pError, pQuery, pRecords) =>
247
+ {
248
+ // Soft-deleted records are excluded
249
+ });
250
+
251
+ // Include soft-deleted records
252
+ meadow.doReads(meadow.query.setDisableDeleteTracking(true),
253
+ (pError, pQuery, pRecords) =>
254
+ {
255
+ // ALL records returned, including soft-deleted ones
256
+ });
257
+ ```
258
+
259
+ ## Slow Query Profiling
260
+
261
+ The `doReads` method (and `doCount`) automatically profiles query execution time. If a query exceeds the configured threshold, a warning is logged:
262
+
263
+ ```javascript
264
+ // Configure the threshold (default: 200ms)
265
+ const _Fable = require('fable').new(
266
+ {
267
+ QueryThresholdWarnTime: 500 // Warn for queries over 500ms
268
+ });
269
+ ```
270
+
271
+ The slow query log includes the provider name, SQL body, parameters, and the fully interpolated query string for easy debugging.
272
+
273
+ ## Raw Query Override
274
+
275
+ Both read operations respect the `Read` raw query override:
276
+
277
+ ```javascript
278
+ // Override the generated SELECT with custom SQL
279
+ meadow.rawQueries.setQuery('Read',
280
+ 'SELECT b.*, a.Name AS AuthorName FROM Book b LEFT JOIN Author a ON b.IDAuthor = a.IDAuthor WHERE b.IDBook = :IDBook');
281
+
282
+ // This custom query is used for doRead
283
+ meadow.doRead(meadow.query.addFilter('IDBook', 1),
284
+ (pError, pQuery, pRecord) =>
285
+ {
286
+ // pRecord will include the AuthorName from the JOIN
287
+ });
288
+ ```
289
+
290
+ For `doReads`, use the `Reads` override key:
291
+
292
+ ```javascript
293
+ meadow.rawQueries.setQuery('Reads',
294
+ 'SELECT b.*, COUNT(r.IDReview) AS ReviewCount FROM Book b LEFT JOIN Review r ON b.IDBook = r.IDBook GROUP BY b.IDBook');
295
+ ```
296
+
297
+ ## Full Example
298
+
299
+ ```javascript
300
+ const libFable = require('fable').new(
301
+ {
302
+ QueryThresholdWarnTime: 300
303
+ });
304
+ const libMeadow = require('meadow');
305
+
306
+ const meadow = libMeadow.new(libFable, 'Book')
307
+ .setProvider('MySQL')
308
+ .setDefaultIdentifier('IDBook')
309
+ .setSchema([
310
+ { Column: 'IDBook', Type: 'AutoIdentity' },
311
+ { Column: 'GUIDBook', Type: 'AutoGUID' },
312
+ { Column: 'Title', Type: 'String', Size: '255' },
313
+ { Column: 'Author', Type: 'String', Size: '128' },
314
+ { Column: 'PublishYear', Type: 'Numeric' },
315
+ { Column: 'Price', Type: 'Decimal', Size: '18,2' },
316
+ { Column: 'CreateDate', Type: 'CreateDate' },
317
+ { Column: 'CreatingIDUser', Type: 'CreateIDUser' },
318
+ { Column: 'Deleted', Type: 'Deleted' }
319
+ ]);
320
+
321
+ // Read a single book
322
+ meadow.doRead(meadow.query.addFilter('IDBook', 1),
323
+ (pError, pQuery, pBook) =>
324
+ {
325
+ if (!pError && pBook)
326
+ {
327
+ console.log('Book:', pBook.Title);
328
+ }
329
+ });
330
+
331
+ // Read a filtered, sorted, paginated list
332
+ const tmpQuery = meadow.query
333
+ .addFilter('PublishYear', 1980, '>=')
334
+ .addSort({ Column: 'Title', Direction: 'ASC' })
335
+ .setCap(10)
336
+ .setBegin(0);
337
+
338
+ meadow.doReads(tmpQuery,
339
+ (pError, pQuery, pBooks) =>
340
+ {
341
+ if (!pError)
342
+ {
343
+ console.log('Post-1980 books (page 1):');
344
+ pBooks.forEach((pBook) =>
345
+ {
346
+ console.log(` ${pBook.Title} (${pBook.PublishYear}) - $${pBook.Price}`);
347
+ });
348
+ }
349
+ });
350
+ ```
@@ -0,0 +1,250 @@
1
+ # Update
2
+
3
+ > Modify existing records in the database
4
+
5
+ The `doUpdate` method modifies an existing record and returns the fully hydrated updated object. Meadow automatically handles audit stamping, safely ignores identity and create-only fields, and reads back the record after updating to ensure you receive the current state.
6
+
7
+ ## Method Signature
8
+
9
+ ```javascript
10
+ meadow.doUpdate(pQuery, fCallBack)
11
+ ```
12
+
13
+ ### Callback
14
+
15
+ ```javascript
16
+ fCallBack(pError, pUpdateQuery, pReadQuery, pRecord)
17
+ ```
18
+
19
+ | Parameter | Type | Description |
20
+ |-----------|------|-------------|
21
+ | `pError` | object/null | Error object if the operation failed, null on success |
22
+ | `pUpdateQuery` | object | The query used for the UPDATE operation |
23
+ | `pReadQuery` | object | The query used to read back the updated record |
24
+ | `pRecord` | object | The fully hydrated record after update |
25
+
26
+ ## Basic Usage
27
+
28
+ ```javascript
29
+ const tmpQuery = meadow.query
30
+ .addRecord(
31
+ {
32
+ IDBook: 42,
33
+ Title: 'Updated Title',
34
+ Price: 19.99
35
+ });
36
+
37
+ meadow.doUpdate(tmpQuery,
38
+ (pError, pUpdateQuery, pReadQuery, pRecord) =>
39
+ {
40
+ if (pError)
41
+ {
42
+ return console.log('Update failed:', pError);
43
+ }
44
+ console.log('Updated:', pRecord.Title, '- New price:', pRecord.Price);
45
+ console.log('Last modified:', pRecord.UpdateDate);
46
+ });
47
+ ```
48
+
49
+ ## How It Works
50
+
51
+ The update operation follows a multi-step waterfall:
52
+
53
+ ```
54
+ 1. Validate and Prepare
55
+ └── Verify record has default identifier, set user ID, add filters
56
+ 2. Execute UPDATE
57
+ └── Run the UPDATE query via provider
58
+ 3. Read Back Record
59
+ └── Fetch the updated record using the same filter
60
+ 4. Marshal to Object
61
+ └── Convert DB result to a plain JavaScript object
62
+ ```
63
+
64
+ ## Record Identification
65
+
66
+ The record you pass to `addRecord()` **must** include the default identifier (e.g., `IDBook`). Meadow uses this to build the WHERE clause:
67
+
68
+ ```javascript
69
+ // The default identifier must be present
70
+ const tmpQuery = meadow.query
71
+ .addRecord(
72
+ {
73
+ IDBook: 42, // Required: identifies which record to update
74
+ Title: 'New Title'
75
+ });
76
+
77
+ meadow.doUpdate(tmpQuery,
78
+ (pError, pUpdateQuery, pReadQuery, pRecord) =>
79
+ {
80
+ // Meadow automatically added: WHERE IDBook = 42
81
+ });
82
+ ```
83
+
84
+ If the default identifier is missing, the operation fails with an error to prevent accidental mass updates.
85
+
86
+ ## Auto-Populated Fields
87
+
88
+ When your schema includes these column types, Meadow handles them automatically during updates:
89
+
90
+ | Schema Type | Behavior on Update |
91
+ |-------------|-------------------|
92
+ | `AutoIdentity` | **Ignored** - cannot modify the primary key |
93
+ | `AutoGUID` | **Ignored** - not modified on update |
94
+ | `CreateDate` | **Ignored** - creation timestamp is immutable |
95
+ | `CreateIDUser` | **Ignored** - creating user is immutable |
96
+ | `UpdateDate` | Set to `NOW()` automatically |
97
+ | `UpdateIDUser` | Set to the current user ID |
98
+ | `DeleteDate` | **Ignored** on standard update |
99
+ | `DeleteIDUser` | **Ignored** on standard update |
100
+ | `Deleted` | Included if present in the record |
101
+
102
+ ## Partial Updates
103
+
104
+ Only fields present in the record are modified. Other columns remain unchanged:
105
+
106
+ ```javascript
107
+ // Only update the Title -- Author, Price, etc. stay the same
108
+ const tmpQuery = meadow.query
109
+ .addRecord(
110
+ {
111
+ IDBook: 42,
112
+ Title: 'Just the Title Changes'
113
+ });
114
+
115
+ meadow.doUpdate(tmpQuery,
116
+ (pError, pUpdateQuery, pReadQuery, pRecord) =>
117
+ {
118
+ // pRecord.Author is unchanged
119
+ // pRecord.Price is unchanged
120
+ // pRecord.Title is 'Just the Title Changes'
121
+ // pRecord.UpdateDate is NOW()
122
+ });
123
+ ```
124
+
125
+ ## Disabling Auto-Stamps
126
+
127
+ For data migration or special operations, you can disable automatic timestamp and user stamping:
128
+
129
+ ```javascript
130
+ const tmpQuery = meadow.query
131
+ .addRecord(
132
+ {
133
+ IDBook: 42,
134
+ Title: 'Migrated Data'
135
+ });
136
+
137
+ // Disable auto-date stamp (UpdateDate will not be set to NOW())
138
+ tmpQuery.query.disableAutoDateStamp = true;
139
+
140
+ // Disable auto-user stamp (UpdateIDUser will not be set)
141
+ tmpQuery.query.disableAutoUserStamp = true;
142
+
143
+ meadow.doUpdate(tmpQuery,
144
+ (pError, pUpdateQuery, pReadQuery, pRecord) =>
145
+ {
146
+ // UpdateDate and UpdatingIDUser were not modified
147
+ });
148
+ ```
149
+
150
+ ## Safety: Filter Requirement
151
+
152
+ Meadow requires at least one filter on the query before executing an update. The default identifier filter is added automatically from the record, but if somehow no filters are present, the operation aborts:
153
+
154
+ ```javascript
155
+ // This will fail safely:
156
+ // "Automated update missing filters... aborting!"
157
+ ```
158
+
159
+ This prevents accidental `UPDATE ... SET ...` without a WHERE clause.
160
+
161
+ ## Error Handling
162
+
163
+ Common error conditions:
164
+
165
+ ```javascript
166
+ meadow.doUpdate(tmpQuery,
167
+ (pError, pUpdateQuery, pReadQuery, pRecord) =>
168
+ {
169
+ if (pError)
170
+ {
171
+ // Possible errors:
172
+ // - "No record submitted" (missing addRecord)
173
+ // - "Automated update missing default identifier"
174
+ // (record doesn't have IDBook or equivalent)
175
+ // - "Automated update missing filters... aborting!"
176
+ // (safety check failed)
177
+ // - "No record updated." (database returned no affected rows)
178
+ // - "No record found to update!" (read-back found nothing)
179
+ // - Provider-specific database errors
180
+ console.log('Error:', pError);
181
+ }
182
+ });
183
+ ```
184
+
185
+ ## Raw Query Override
186
+
187
+ The Update operation does not support raw query overrides for the UPDATE step itself. However, the read-back step respects the `Read` override, so custom JOINs and computed columns appear in the returned record.
188
+
189
+ ## Full Example
190
+
191
+ ```javascript
192
+ const libFable = require('fable').new();
193
+ const libMeadow = require('meadow');
194
+
195
+ const meadow = libMeadow.new(libFable, 'Book')
196
+ .setProvider('MySQL')
197
+ .setDefaultIdentifier('IDBook')
198
+ .setSchema([
199
+ { Column: 'IDBook', Type: 'AutoIdentity' },
200
+ { Column: 'GUIDBook', Type: 'AutoGUID' },
201
+ { Column: 'Title', Type: 'String', Size: '255' },
202
+ { Column: 'Author', Type: 'String', Size: '128' },
203
+ { Column: 'Price', Type: 'Decimal', Size: '18,2' },
204
+ { Column: 'InPrint', Type: 'Boolean' },
205
+ { Column: 'CreateDate', Type: 'CreateDate' },
206
+ { Column: 'CreatingIDUser', Type: 'CreateIDUser' },
207
+ { Column: 'UpdateDate', Type: 'UpdateDate' },
208
+ { Column: 'UpdatingIDUser', Type: 'UpdateIDUser' },
209
+ { Column: 'Deleted', Type: 'Deleted' }
210
+ ]);
211
+
212
+ // Set the user performing the update
213
+ meadow.setIDUser(5);
214
+
215
+ // Update the price and print status of book 42
216
+ const tmpQuery = meadow.query
217
+ .addRecord(
218
+ {
219
+ IDBook: 42,
220
+ Price: 24.99,
221
+ InPrint: true
222
+ });
223
+
224
+ meadow.doUpdate(tmpQuery,
225
+ (pError, pUpdateQuery, pReadQuery, pRecord) =>
226
+ {
227
+ if (pError)
228
+ {
229
+ return console.log('Update failed:', pError);
230
+ }
231
+
232
+ // pRecord now contains the full updated record:
233
+ // {
234
+ // IDBook: 42, (unchanged)
235
+ // GUIDBook: '0x...', (unchanged)
236
+ // Title: 'Original Title', (unchanged)
237
+ // Author: 'Original Author', (unchanged)
238
+ // Price: 24.99, (updated)
239
+ // InPrint: true, (updated)
240
+ // CreateDate: '2024-01-15...', (unchanged)
241
+ // CreatingIDUser: 1, (unchanged)
242
+ // UpdateDate: '2024-06-20...', (auto: NOW())
243
+ // UpdatingIDUser: 5, (auto: from setIDUser)
244
+ // Deleted: 0 (unchanged)
245
+ // }
246
+ console.log('Updated book', pRecord.IDBook);
247
+ console.log('New price:', pRecord.Price);
248
+ console.log('Modified at:', pRecord.UpdateDate, 'by user', pRecord.UpdatingIDUser);
249
+ });
250
+ ```