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,173 @@
1
+ # SQLite Provider
2
+
3
+ > Lightweight embedded SQL for local development and compact deployments
4
+
5
+ The SQLite provider connects Meadow to SQLite databases, offering a zero-configuration embedded database option. It's ideal for local development, testing, and applications that need a lightweight persistent store without a separate database server.
6
+
7
+ ## Setup
8
+
9
+ ### Install Dependencies
10
+
11
+ ```bash
12
+ npm install meadow meadow-connection-sqlite
13
+ ```
14
+
15
+ ### Register the Connection
16
+
17
+ ```javascript
18
+ const libFable = require('fable').new();
19
+ const libMeadowConnectionSQLite = require('meadow-connection-sqlite');
20
+
21
+ // Register the connection service
22
+ libFable.serviceManager.addServiceType('MeadowMySQLProvider', libMeadowConnectionSQLite);
23
+ libFable.serviceManager.instantiateServiceProvider('MeadowMySQLProvider');
24
+ ```
25
+
26
+ ### Create a Meadow DAL
27
+
28
+ ```javascript
29
+ const libMeadow = require('meadow');
30
+
31
+ const meadow = libMeadow.new(libFable, 'Book')
32
+ .setProvider('SQLite')
33
+ .setDefaultIdentifier('IDBook')
34
+ .setSchema([
35
+ { Column: 'IDBook', Type: 'AutoIdentity' },
36
+ { Column: 'GUIDBook', Type: 'AutoGUID' },
37
+ { Column: 'Title', Type: 'String', Size: '255' },
38
+ { Column: 'Author', Type: 'String', Size: '128' },
39
+ { Column: 'CreateDate', Type: 'CreateDate' },
40
+ { Column: 'CreatingIDUser', Type: 'CreateIDUser' },
41
+ { Column: 'UpdateDate', Type: 'UpdateDate' },
42
+ { Column: 'UpdatingIDUser', Type: 'UpdateIDUser' },
43
+ { Column: 'DeleteDate', Type: 'DeleteDate' },
44
+ { Column: 'DeletingIDUser', Type: 'DeleteIDUser' },
45
+ { Column: 'Deleted', Type: 'Deleted' }
46
+ ]);
47
+ ```
48
+
49
+ ## Connection Management
50
+
51
+ The SQLite provider uses the same connection pool interface as the MySQL provider. Each operation:
52
+
53
+ 1. Acquires a connection via `pool.getConnection()`
54
+ 2. Executes the generated SQL query
55
+ 3. Releases the connection back to the pool
56
+
57
+ ## CRUD Operations
58
+
59
+ ### Create
60
+
61
+ Executes an INSERT and returns the auto-generated identity value.
62
+
63
+ ```javascript
64
+ meadow.doCreate(
65
+ meadow.query.addRecord({ Title: 'Dune', Author: 'Frank Herbert' }),
66
+ (pError, pCreateQuery, pReadQuery, pRecord) =>
67
+ {
68
+ console.log('New ID:', pRecord.IDBook);
69
+ });
70
+ ```
71
+
72
+ **Internally:**
73
+ - Builds query with SQLite dialect: `pQuery.setDialect('SQLite').buildCreateQuery()`
74
+ - Extracts identity: `result.insertId`
75
+
76
+ ### Read
77
+
78
+ Executes a SELECT and returns result rows.
79
+
80
+ ```javascript
81
+ meadow.doRead(
82
+ meadow.query.addFilter('IDBook', 42),
83
+ (pError, pQuery, pRecord) =>
84
+ {
85
+ console.log('Title:', pRecord.Title);
86
+ });
87
+
88
+ meadow.doReads(
89
+ meadow.query.setCap(25).setBegin(0),
90
+ (pError, pQuery, pRecords) =>
91
+ {
92
+ console.log('Found', pRecords.length, 'books');
93
+ });
94
+ ```
95
+
96
+ ### Update
97
+
98
+ Executes an UPDATE statement.
99
+
100
+ ```javascript
101
+ meadow.doUpdate(
102
+ meadow.query
103
+ .addFilter('IDBook', 42)
104
+ .addRecord({ Title: 'Updated Title' }),
105
+ (pError, pUpdateQuery, pReadQuery, pRecord) =>
106
+ {
107
+ console.log('Updated:', pRecord.Title);
108
+ });
109
+ ```
110
+
111
+ ### Delete
112
+
113
+ Executes a soft delete and returns the affected row count.
114
+
115
+ ```javascript
116
+ meadow.doDelete(
117
+ meadow.query.addFilter('IDBook', 42),
118
+ (pError, pQuery, pResult) =>
119
+ {
120
+ console.log('Deleted rows:', pResult);
121
+ });
122
+ ```
123
+
124
+ ### Undelete
125
+
126
+ Reverses a soft delete.
127
+
128
+ ```javascript
129
+ meadow.doUndelete(
130
+ meadow.query.addFilter('IDBook', 42),
131
+ (pError, pQuery, pResult) =>
132
+ {
133
+ console.log('Restored rows:', pResult);
134
+ });
135
+ ```
136
+
137
+ ### Count
138
+
139
+ Returns the matching record count.
140
+
141
+ ```javascript
142
+ meadow.doCount(
143
+ meadow.query,
144
+ (pError, pQuery, pCount) =>
145
+ {
146
+ console.log('Total books:', pCount);
147
+ });
148
+ ```
149
+
150
+ ## When to Use SQLite
151
+
152
+ | Use Case | Recommendation |
153
+ |----------|---------------|
154
+ | Local development | Great — no server setup required |
155
+ | Unit testing | Good — fast, in-process database |
156
+ | Small production apps | Good — for low-concurrency workloads |
157
+ | High-concurrency production | Consider MySQL or MSSQL instead |
158
+ | Browser applications | Use ALASQL provider instead |
159
+
160
+ ## Error Handling
161
+
162
+ The SQLite provider follows the same error handling pattern as MySQL:
163
+
164
+ - Database errors are stored in `pQuery.parameters.result.error`
165
+ - Identity/rowcount extraction failures are logged as warnings
166
+ - Connection errors bubble through the callback
167
+
168
+ ## Related Documentation
169
+
170
+ - [Providers Overview](providers/README.md) — Comparison of all providers
171
+ - [MySQL Provider](providers/mysql.md) — MySQL/MariaDB for production
172
+ - [ALASQL Provider](providers/alasql.md) — In-memory alternative
173
+ - [meadow-connection-sqlite](https://github.com/stevenvelozo/meadow-connection-sqlite) — Connection module source
@@ -0,0 +1,175 @@
1
+ # Query Objects
2
+
3
+ > Building and configuring queries with the FoxHound DSL
4
+
5
+ Every data operation in Meadow begins with a query object. Meadow uses [FoxHound](https://github.com/stevenvelozo/foxhound) as its query DSL, generating dialect-specific SQL from a fluent, chainable API. You never need to construct FoxHound directly -- Meadow gives you a fresh, pre-configured query every time you access the `.query` property.
6
+
7
+ ## Getting a Query Object
8
+
9
+ ```javascript
10
+ const tmpQuery = meadow.query;
11
+ ```
12
+
13
+ This returns a **cloned** FoxHound instance with the entity scope and schema already set. Each call to `.query` returns a new independent clone, so queries never leak state between operations.
14
+
15
+ ```javascript
16
+ // These are two completely separate queries
17
+ const tmpQueryA = meadow.query.addFilter('IDBook', 1);
18
+ const tmpQueryB = meadow.query.addFilter('Author', 'Asimov');
19
+
20
+ // tmpQueryA and tmpQueryB do not affect each other
21
+ ```
22
+
23
+ ## Chainable Methods
24
+
25
+ Query methods return the query object itself, so you can chain them together fluently.
26
+
27
+ ### Filtering
28
+
29
+ ```javascript
30
+ // Simple equality filter
31
+ meadow.query.addFilter('IDBook', 42)
32
+
33
+ // Multiple filters (AND)
34
+ meadow.query
35
+ .addFilter('Author', 'Herbert')
36
+ .addFilter('InPrint', 1)
37
+
38
+ // Filter with operator
39
+ meadow.query.addFilter('Price', 20, '>')
40
+
41
+ // Filter with IN clause (array value)
42
+ meadow.query.addFilter('IDCategory', [1, 3, 5])
43
+ ```
44
+
45
+ Supported filter operators: `=`, `!=`, `>`, `<`, `>=`, `<=`, `LIKE`, `IN`
46
+
47
+ ### Pagination
48
+
49
+ ```javascript
50
+ // Limit to 25 records starting from record 50
51
+ meadow.query
52
+ .setCap(25)
53
+ .setBegin(50)
54
+ ```
55
+
56
+ | Method | Description |
57
+ |--------|-------------|
58
+ | `setCap(pNumber)` | Maximum records to return (SQL `LIMIT`) |
59
+ | `setBegin(pNumber)` | Starting offset (SQL `OFFSET`) |
60
+
61
+ ### Sorting
62
+
63
+ ```javascript
64
+ // Sort by Title ascending
65
+ meadow.query.addSort({ Column: 'Title', Direction: 'ASC' })
66
+
67
+ // Multiple sort columns
68
+ meadow.query
69
+ .addSort({ Column: 'Author', Direction: 'ASC' })
70
+ .addSort({ Column: 'PublishDate', Direction: 'DESC' })
71
+ ```
72
+
73
+ ### Column Selection
74
+
75
+ ```javascript
76
+ // Only retrieve specific columns
77
+ meadow.query.setDataElements(['IDBook', 'Title', 'Author'])
78
+ ```
79
+
80
+ ### Distinct Queries
81
+
82
+ ```javascript
83
+ // Return only unique combinations
84
+ meadow.query
85
+ .setDistinct(true)
86
+ .setDataElements(['Author', 'Publisher'])
87
+ ```
88
+
89
+ ### Records for Create and Update
90
+
91
+ ```javascript
92
+ // Add a record for insert or update
93
+ meadow.query.addRecord({ Title: 'Neuromancer', Author: 'William Gibson' })
94
+ ```
95
+
96
+ The `addRecord()` method attaches data that will be used by create and update operations. For updates, only the fields present in the record will be modified.
97
+
98
+ ### Delete Tracking
99
+
100
+ ```javascript
101
+ // Include soft-deleted records in results
102
+ meadow.query.setDisableDeleteTracking(true)
103
+ ```
104
+
105
+ By default, queries automatically exclude records where the `Deleted` column is `1`. Use `setDisableDeleteTracking(true)` to include them.
106
+
107
+ ## Query Lifecycle
108
+
109
+ When you pass a query to a CRUD method, Meadow handles the rest:
110
+
111
+ ```
112
+ 1. meadow.query → Clone a fresh FoxHound query with scope and schema
113
+ 2. .addFilter(...) → Configure the query parameters
114
+ 3. .addRecord(...) → Attach data (for create/update)
115
+ 4. meadow.doRead(query) → Meadow sets the dialect, builds SQL, executes via provider
116
+ 5. callback(error, ...) → Results returned through callback
117
+ ```
118
+
119
+ You never need to call `setDialect()` or `buildReadQuery()` yourself -- Meadow's behavior modules handle dialect selection and SQL generation based on the configured provider.
120
+
121
+ ## User Identity
122
+
123
+ Meadow stamps user identity into create, update, and delete operations automatically. Set the user ID before operations:
124
+
125
+ ```javascript
126
+ // Set user ID for audit stamping
127
+ meadow.setIDUser(currentUserID);
128
+
129
+ // Or set it per-query
130
+ const tmpQuery = meadow.query;
131
+ tmpQuery.query.IDUser = currentUserID;
132
+ ```
133
+
134
+ This user ID is written to `CreateIDUser`, `UpdateIDUser`, and `DeleteIDUser` columns when those schema types are present.
135
+
136
+ ### Disabling Auto-Stamps
137
+
138
+ For special cases like data migration, you can disable automatic stamping:
139
+
140
+ ```javascript
141
+ const tmpQuery = meadow.query.addRecord(migrationRecord);
142
+ tmpQuery.query.disableAutoDateStamp = true; // Skip UpdateDate = NOW()
143
+ tmpQuery.query.disableAutoUserStamp = true; // Skip UpdateIDUser = :IDUser
144
+ ```
145
+
146
+ ## Query State
147
+
148
+ Under the hood, the query object carries state that Meadow and the provider use:
149
+
150
+ | Property | Description |
151
+ |----------|-------------|
152
+ | `query.scope` | Entity/table name |
153
+ | `query.cap` | Record limit |
154
+ | `query.begin` | Record offset |
155
+ | `query.dataElements` | Selected columns |
156
+ | `query.sort` | Sort configuration |
157
+ | `query.filter` | Filter conditions |
158
+ | `query.records` | Records for create/update |
159
+ | `query.schema` | Column schema definitions |
160
+ | `query.IDUser` | User ID for audit stamps |
161
+ | `query.disableDeleteTracking` | Include soft-deleted records |
162
+ | `query.disableAutoDateStamp` | Skip auto date stamps |
163
+ | `query.disableAutoUserStamp` | Skip auto user stamps |
164
+ | `result.executed` | Whether the query has been run |
165
+ | `result.value` | Result data (ID, rows, count) |
166
+
167
+ ## CRUD Operations
168
+
169
+ Each CRUD operation uses the query object differently. See the detailed documentation for each:
170
+
171
+ - [Create](create.md) - Insert new records
172
+ - [Read](read.md) - Retrieve single and multiple records
173
+ - [Update](update.md) - Modify existing records
174
+ - [Delete](delete.md) - Soft delete and undelete records
175
+ - [Count](count.md) - Count records matching criteria
@@ -0,0 +1,228 @@
1
+ # Count
2
+
3
+ > Count records matching query criteria
4
+
5
+ The `doCount` method returns the number of records matching your query filters. It automatically excludes soft-deleted records (unless disabled) and includes slow query profiling for performance monitoring.
6
+
7
+ ## Method Signature
8
+
9
+ ```javascript
10
+ meadow.doCount(pQuery, fCallBack)
11
+ ```
12
+
13
+ ### Callback
14
+
15
+ ```javascript
16
+ fCallBack(pError, pQuery, pCount)
17
+ ```
18
+
19
+ | Parameter | Type | Description |
20
+ |-----------|------|-------------|
21
+ | `pError` | object/null | Error object if the operation failed, null on success |
22
+ | `pQuery` | object | The query that was executed |
23
+ | `pCount` | number | The record count |
24
+
25
+ ## Basic Usage
26
+
27
+ ```javascript
28
+ // Count all records
29
+ meadow.doCount(meadow.query,
30
+ (pError, pQuery, pCount) =>
31
+ {
32
+ if (pError)
33
+ {
34
+ return console.log('Count failed:', pError);
35
+ }
36
+ console.log('Total books:', pCount);
37
+ });
38
+ ```
39
+
40
+ ## Filtered Counts
41
+
42
+ ```javascript
43
+ // Count books by a specific author
44
+ meadow.doCount(meadow.query.addFilter('Author', 'Asimov'),
45
+ (pError, pQuery, pCount) =>
46
+ {
47
+ console.log('Asimov books:', pCount);
48
+ });
49
+
50
+ // Count books published after 2000
51
+ meadow.doCount(meadow.query.addFilter('PublishYear', 2000, '>'),
52
+ (pError, pQuery, pCount) =>
53
+ {
54
+ console.log('Post-2000 books:', pCount);
55
+ });
56
+
57
+ // Count with multiple filters
58
+ const tmpQuery = meadow.query
59
+ .addFilter('Author', 'Herbert')
60
+ .addFilter('InPrint', 1);
61
+
62
+ meadow.doCount(tmpQuery,
63
+ (pError, pQuery, pCount) =>
64
+ {
65
+ console.log('Herbert books in print:', pCount);
66
+ });
67
+ ```
68
+
69
+ ## How It Works
70
+
71
+ ```
72
+ 1. Build Count Query
73
+ └── Apply filters, set dialect, generate SELECT COUNT(*) SQL
74
+ 2. Execute via Provider
75
+ └── Run query against database, start performance timer
76
+ 3. Validate and Profile
77
+ └── Verify result is a number, log warning if query exceeded threshold
78
+ ```
79
+
80
+ ## Soft Delete Filtering
81
+
82
+ By default, the count excludes soft-deleted records:
83
+
84
+ ```javascript
85
+ // Count only active records (default)
86
+ meadow.doCount(meadow.query,
87
+ (pError, pQuery, pActiveCount) =>
88
+ {
89
+ console.log('Active books:', pActiveCount);
90
+ });
91
+
92
+ // Count ALL records including soft-deleted
93
+ meadow.doCount(meadow.query.setDisableDeleteTracking(true),
94
+ (pError, pQuery, pTotalCount) =>
95
+ {
96
+ console.log('Total books (including deleted):', pTotalCount);
97
+ });
98
+ ```
99
+
100
+ ## Slow Query Profiling
101
+
102
+ Like `doReads`, the count operation profiles execution time and logs a warning for slow queries:
103
+
104
+ ```javascript
105
+ // Configure the threshold (default: 200ms)
106
+ const _Fable = require('fable').new(
107
+ {
108
+ QueryThresholdWarnTime: 500
109
+ });
110
+ ```
111
+
112
+ When a count query exceeds the threshold, Meadow logs a warning with the provider name, SQL body, parameters, and full interpolated query.
113
+
114
+ ## Pagination Pattern
115
+
116
+ Count is commonly used together with `doReads` to implement pagination:
117
+
118
+ ```javascript
119
+ const PAGE_SIZE = 25;
120
+ let currentPage = 0;
121
+
122
+ // First, get the total count
123
+ meadow.doCount(meadow.query.addFilter('Author', 'Asimov'),
124
+ (pError, pQuery, pTotalCount) =>
125
+ {
126
+ const totalPages = Math.ceil(pTotalCount / PAGE_SIZE);
127
+ console.log('Total records:', pTotalCount, 'Pages:', totalPages);
128
+
129
+ // Then fetch the current page
130
+ const tmpQuery = meadow.query
131
+ .addFilter('Author', 'Asimov')
132
+ .setCap(PAGE_SIZE)
133
+ .setBegin(currentPage * PAGE_SIZE)
134
+ .addSort({ Column: 'Title', Direction: 'ASC' });
135
+
136
+ meadow.doReads(tmpQuery,
137
+ (pError, pQuery, pRecords) =>
138
+ {
139
+ console.log(`Page ${currentPage + 1} of ${totalPages}:`);
140
+ pRecords.forEach((pBook) =>
141
+ {
142
+ console.log(' -', pBook.Title);
143
+ });
144
+ });
145
+ });
146
+ ```
147
+
148
+ ## Error Handling
149
+
150
+ ```javascript
151
+ meadow.doCount(meadow.query,
152
+ (pError, pQuery, pCount) =>
153
+ {
154
+ if (pError)
155
+ {
156
+ // Possible errors:
157
+ // - "Count did not return valid results."
158
+ // (provider returned non-numeric value)
159
+ // - Provider-specific database errors
160
+ console.log('Error:', pError);
161
+ }
162
+ });
163
+ ```
164
+
165
+ ## Raw Query Override
166
+
167
+ The count operation respects the `Count` raw query override:
168
+
169
+ ```javascript
170
+ // Override with a custom count query
171
+ meadow.rawQueries.setQuery('Count',
172
+ 'SELECT COUNT(DISTINCT Author) AS RowCount FROM Book WHERE Deleted = 0');
173
+
174
+ meadow.doCount(meadow.query,
175
+ (pError, pQuery, pCount) =>
176
+ {
177
+ // pCount is the number of unique authors
178
+ });
179
+ ```
180
+
181
+ ## Full Example
182
+
183
+ ```javascript
184
+ const libFable = require('fable').new(
185
+ {
186
+ QueryThresholdWarnTime: 300
187
+ });
188
+ const libMeadow = require('meadow');
189
+
190
+ const meadow = libMeadow.new(libFable, 'Book')
191
+ .setProvider('MySQL')
192
+ .setDefaultIdentifier('IDBook')
193
+ .setSchema([
194
+ { Column: 'IDBook', Type: 'AutoIdentity' },
195
+ { Column: 'Title', Type: 'String', Size: '255' },
196
+ { Column: 'Author', Type: 'String', Size: '128' },
197
+ { Column: 'PublishYear', Type: 'Numeric' },
198
+ { Column: 'Deleted', Type: 'Deleted' }
199
+ ]);
200
+
201
+ // Count all active books
202
+ meadow.doCount(meadow.query,
203
+ (pError, pQuery, pTotal) =>
204
+ {
205
+ console.log('Total active books:', pTotal);
206
+ });
207
+
208
+ // Count by author
209
+ meadow.doCount(meadow.query.addFilter('Author', 'Bradbury'),
210
+ (pError, pQuery, pCount) =>
211
+ {
212
+ console.log('Bradbury books:', pCount);
213
+ });
214
+
215
+ // Count including soft-deleted for an audit report
216
+ meadow.doCount(meadow.query.setDisableDeleteTracking(true),
217
+ (pError, pQuery, pAllCount) =>
218
+ {
219
+ meadow.doCount(meadow.query,
220
+ (pError, pQuery, pActiveCount) =>
221
+ {
222
+ const deletedCount = pAllCount - pActiveCount;
223
+ console.log('Active:', pActiveCount);
224
+ console.log('Deleted:', deletedCount);
225
+ console.log('Total:', pAllCount);
226
+ });
227
+ });
228
+ ```