meadow 2.0.17 → 2.0.20
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/CONTRIBUTING.md +50 -0
- package/README.md +176 -110
- package/docs/README.md +276 -0
- package/docs/_sidebar.md +38 -0
- package/docs/index.html +39 -0
- package/docs/providers/README.md +253 -0
- package/docs/providers/alasql.md +271 -0
- package/docs/providers/mssql.md +296 -0
- package/docs/providers/mysql.md +260 -0
- package/docs/providers/sqlite.md +173 -0
- package/docs/query/README.md +175 -0
- package/docs/query/count.md +228 -0
- package/docs/query/create.md +226 -0
- package/docs/query/delete.md +264 -0
- package/docs/query/read.md +350 -0
- package/docs/query/update.md +250 -0
- package/docs/schema/README.md +408 -0
- package/package.json +19 -6
- package/scripts/mssql-test-db.sh +111 -0
- package/scripts/mysql-test-db.sh +108 -0
- package/source/Meadow.js +1 -0
- package/source/providers/Meadow-Provider-SQLite.js +269 -154
- package/test/Meadow-Provider-SQLite-AnimalReadQuery.sql +5 -0
- package/test/Meadow-Provider-SQLite_tests.js +931 -0
package/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Contributing to Retold
|
|
2
|
+
|
|
3
|
+
We welcome contributions to Retold and its modules. This guide covers the expectations and process for contributing.
|
|
4
|
+
|
|
5
|
+
## Code of Conduct
|
|
6
|
+
|
|
7
|
+
The Retold community values **empathy**, **equity**, **kindness**, and **thoughtfulness**. We expect all participants to treat each other with respect, assume good intent, and engage constructively. These values apply to all interactions: pull requests, issues, discussions, and code review.
|
|
8
|
+
|
|
9
|
+
## How to Contribute
|
|
10
|
+
|
|
11
|
+
### Pull Requests
|
|
12
|
+
|
|
13
|
+
Pull requests are the preferred method for contributing changes. To submit one:
|
|
14
|
+
|
|
15
|
+
1. Fork the module repository you want to change
|
|
16
|
+
2. Create a branch for your work
|
|
17
|
+
3. Make your changes, following the code style of the module you are editing
|
|
18
|
+
4. Ensure your changes have test coverage (see below)
|
|
19
|
+
5. Open a pull request against the module's main branch
|
|
20
|
+
|
|
21
|
+
**Submitting a pull request does not guarantee it will be accepted.** Maintainers review contributions for fit, quality, and alignment with the project's direction. A PR may be declined, or you may be asked to revise it. This is normal and not a reflection on the quality of your effort.
|
|
22
|
+
|
|
23
|
+
### Reporting Issues
|
|
24
|
+
|
|
25
|
+
If you find a bug or have a feature suggestion, open an issue on the relevant module's repository. Include enough detail to reproduce the problem or understand the proposal.
|
|
26
|
+
|
|
27
|
+
## Test Coverage
|
|
28
|
+
|
|
29
|
+
Every commit must include test coverage for the changes it introduces. Retold modules use Mocha in TDD style. Before submitting:
|
|
30
|
+
|
|
31
|
+
- **Write tests** for any new functionality or bug fixes
|
|
32
|
+
- **Run the existing test suite** with `npm test` and confirm all tests pass
|
|
33
|
+
- **Check coverage** with `npm run coverage` if the module supports it
|
|
34
|
+
|
|
35
|
+
Pull requests that break existing tests or lack coverage for new code will not be merged.
|
|
36
|
+
|
|
37
|
+
## Code Style
|
|
38
|
+
|
|
39
|
+
Follow the conventions of the module you are working in. The general Retold style is:
|
|
40
|
+
|
|
41
|
+
- **Tabs** for indentation, never spaces
|
|
42
|
+
- **Plain JavaScript** only (no TypeScript)
|
|
43
|
+
- **Allman-style braces** (opening brace on its own line)
|
|
44
|
+
- **Variable naming:** `pVariable` for parameters, `tmpVariable` for temporaries, `libSomething` for imports
|
|
45
|
+
|
|
46
|
+
When in doubt, match what the surrounding code does.
|
|
47
|
+
|
|
48
|
+
## Repository Structure
|
|
49
|
+
|
|
50
|
+
Each module is its own git repository. The [retold](https://github.com/stevenvelozo/retold) repository tracks module organization but does not contain module source code. Direct your pull request to the specific module repository where your change belongs.
|
package/README.md
CHANGED
|
@@ -1,153 +1,219 @@
|
|
|
1
|
-
Meadow
|
|
2
|
-
======
|
|
1
|
+
# Meadow
|
|
3
2
|
|
|
4
|
-
A
|
|
3
|
+
> A data access layer providing magic where you want it, programmability where you don't
|
|
5
4
|
|
|
6
|
-
|
|
5
|
+
Meadow is a JavaScript data broker that handles repetitive CRUD operations through a consistent, provider-agnostic interface. It doesn't care whether your data lives in MySQL, MSSQL, SQLite, or an in-browser IndexedDB store -- Meadow provides the schema management, query building, and data marshalling while you provide the business logic.
|
|
7
6
|
|
|
8
|
-
##
|
|
7
|
+
## Features
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
- **Provider-Agnostic Design** - Pluggable database backends through a consistent interface
|
|
10
|
+
- **Schema-Driven** - Define your data model once, get validation, default objects, and audit tracking for free
|
|
11
|
+
- **FoxHound Query DSL** - Fluent query builder that generates dialect-specific SQL
|
|
12
|
+
- **Automatic Audit Tracking** - Auto-populated create/update/delete timestamps and user stamps
|
|
13
|
+
- **Soft Deletes** - Built-in logical deletion with automatic query filtering
|
|
14
|
+
- **GUID Uniqueness** - Automatic GUID generation and uniqueness enforcement on create
|
|
15
|
+
- **Raw Query Overrides** - Escape hatch for custom SQL when the DSL isn't enough
|
|
16
|
+
- **Fable Integration** - First-class service in the Fable ecosystem with logging and configuration
|
|
17
|
+
|
|
18
|
+
## Quick Start
|
|
19
|
+
|
|
20
|
+
```javascript
|
|
21
|
+
const libFable = require('fable').new();
|
|
22
|
+
const libMeadow = require('meadow');
|
|
23
|
+
|
|
24
|
+
// Create a Meadow DAL for the "Book" entity
|
|
25
|
+
const meadow = libMeadow.new(libFable, 'Book')
|
|
26
|
+
.setProvider('MySQL')
|
|
27
|
+
.setDefaultIdentifier('IDBook')
|
|
28
|
+
.setSchema([
|
|
29
|
+
{ Column: 'IDBook', Type: 'AutoIdentity' },
|
|
30
|
+
{ Column: 'GUIDBook', Type: 'AutoGUID' },
|
|
31
|
+
{ Column: 'Title', Type: 'String', Size: '255' },
|
|
32
|
+
{ Column: 'Author', Type: 'String', Size: '128' },
|
|
33
|
+
{ Column: 'CreateDate', Type: 'CreateDate' },
|
|
34
|
+
{ Column: 'CreatingIDUser', Type: 'CreateIDUser' },
|
|
35
|
+
{ Column: 'UpdateDate', Type: 'UpdateDate' },
|
|
36
|
+
{ Column: 'UpdatingIDUser', Type: 'UpdateIDUser' },
|
|
37
|
+
{ Column: 'DeleteDate', Type: 'DeleteDate' },
|
|
38
|
+
{ Column: 'DeletingIDUser', Type: 'DeleteIDUser' },
|
|
39
|
+
{ Column: 'Deleted', Type: 'Deleted' }
|
|
40
|
+
]);
|
|
41
|
+
|
|
42
|
+
// Read a single record
|
|
43
|
+
meadow.doRead(meadow.query.addFilter('IDBook', 42),
|
|
44
|
+
(pError, pQuery, pBook) =>
|
|
45
|
+
{
|
|
46
|
+
if (pError)
|
|
47
|
+
{
|
|
48
|
+
return console.log('Error reading book:', pError);
|
|
49
|
+
}
|
|
50
|
+
console.log('Found:', pBook.Title, 'by', pBook.Author);
|
|
51
|
+
});
|
|
12
52
|
```
|
|
13
53
|
|
|
14
|
-
|
|
54
|
+
## Installation
|
|
15
55
|
|
|
16
|
-
```
|
|
17
|
-
|
|
56
|
+
```bash
|
|
57
|
+
npm install meadow
|
|
18
58
|
```
|
|
19
59
|
|
|
20
|
-
##
|
|
60
|
+
## How It Works
|
|
21
61
|
|
|
22
|
-
|
|
62
|
+
Meadow follows the Fable service provider pattern. You create a DAL instance for each entity in your data model, configure its schema and provider, then use the CRUD methods to interact with your database. Meadow handles query generation, data marshalling, schema validation, and audit stamping automatically.
|
|
23
63
|
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
Database: "sales_data",
|
|
34
|
-
ConnectionPoolLimit: 20
|
|
35
|
-
}
|
|
36
|
-
};
|
|
64
|
+
```
|
|
65
|
+
Fable (Core)
|
|
66
|
+
└── Meadow (Data Access Layer)
|
|
67
|
+
├── Schema (Column definitions, validation, defaults)
|
|
68
|
+
├── FoxHound Query DSL (Dialect-specific SQL generation)
|
|
69
|
+
├── Behaviors (Create, Read, Reads, Update, Delete, Count, Undelete)
|
|
70
|
+
└── Provider (MySQL, MSSQL, SQLite, ALASQL, MeadowEndpoints, None)
|
|
71
|
+
└── Database Connection
|
|
72
|
+
```
|
|
37
73
|
|
|
38
|
-
|
|
74
|
+
## CRUD Operations
|
|
39
75
|
|
|
40
|
-
|
|
41
|
-
var libMySQL = require('mysql2');
|
|
42
|
-
fable.MeadowMySQLConnectionPool = libMySQL.createPool
|
|
43
|
-
(
|
|
44
|
-
{
|
|
45
|
-
connectionLimit: _Fable.settings.MySQL.ConnectionPoolLimit,
|
|
46
|
-
host: _Fable.settings.MySQL.Server,
|
|
47
|
-
port: _Fable.settings.MySQL.Port,
|
|
48
|
-
user: _Fable.settings.MySQL.User,
|
|
49
|
-
password: _Fable.settings.MySQL.Password,
|
|
50
|
-
database: _Fable.settings.MySQL.Database,
|
|
51
|
-
namedPlaceholders: true
|
|
52
|
-
}
|
|
53
|
-
);
|
|
76
|
+
All operations follow an async waterfall pattern with a consistent callback signature.
|
|
54
77
|
|
|
78
|
+
### Create
|
|
55
79
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
80
|
+
```javascript
|
|
81
|
+
const tmpQuery = meadow.query.addRecord({ Title: 'Dune', Author: 'Frank Herbert' });
|
|
82
|
+
meadow.doCreate(tmpQuery,
|
|
83
|
+
(pError, pCreateQuery, pReadQuery, pRecord) =>
|
|
84
|
+
{
|
|
85
|
+
console.log('Created book with ID:', pRecord.IDBook);
|
|
86
|
+
});
|
|
87
|
+
```
|
|
60
88
|
|
|
61
|
-
|
|
62
|
-
|
|
89
|
+
### Read
|
|
90
|
+
|
|
91
|
+
```javascript
|
|
92
|
+
// Single record
|
|
93
|
+
meadow.doRead(meadow.query.addFilter('IDBook', 1),
|
|
94
|
+
(pError, pQuery, pRecord) =>
|
|
95
|
+
{
|
|
96
|
+
console.log('Book:', pRecord.Title);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// Multiple records with pagination
|
|
100
|
+
meadow.doReads(meadow.query.setCap(25).setBegin(0),
|
|
101
|
+
(pError, pQuery, pRecords) =>
|
|
102
|
+
{
|
|
103
|
+
console.log('Found', pRecords.length, 'books');
|
|
104
|
+
});
|
|
105
|
+
```
|
|
63
106
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}
|
|
76
|
-
console.log('Found customer ID '+customer.customerID+' who is named '+customer.name);
|
|
77
|
-
}
|
|
78
|
-
);
|
|
107
|
+
### Update
|
|
108
|
+
|
|
109
|
+
```javascript
|
|
110
|
+
const tmpQuery = meadow.query
|
|
111
|
+
.addFilter('IDBook', 1)
|
|
112
|
+
.addRecord({ Title: 'Updated Title' });
|
|
113
|
+
meadow.doUpdate(tmpQuery,
|
|
114
|
+
(pError, pUpdateQuery, pReadQuery, pRecord) =>
|
|
115
|
+
{
|
|
116
|
+
console.log('Updated:', pRecord.Title);
|
|
117
|
+
});
|
|
79
118
|
```
|
|
80
119
|
|
|
81
|
-
|
|
120
|
+
### Delete and Count
|
|
121
|
+
|
|
122
|
+
```javascript
|
|
123
|
+
// Delete (soft delete if schema supports it)
|
|
124
|
+
meadow.doDelete(meadow.query.addFilter('IDBook', 1),
|
|
125
|
+
(pError, pQuery, pResult) =>
|
|
126
|
+
{
|
|
127
|
+
console.log('Deleted:', pResult, 'record(s)');
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// Count records
|
|
131
|
+
meadow.doCount(meadow.query,
|
|
132
|
+
(pError, pQuery, pCount) =>
|
|
133
|
+
{
|
|
134
|
+
console.log('Total books:', pCount);
|
|
135
|
+
});
|
|
136
|
+
```
|
|
82
137
|
|
|
83
|
-
|
|
84
|
-
// These settings are read automatically from the fable.settings object by meadow
|
|
85
|
-
var fable = require('fable').new();
|
|
138
|
+
## Schema
|
|
86
139
|
|
|
87
|
-
|
|
88
|
-
var libALASQL = require('alasql');
|
|
89
|
-
libALASQL('CREATE INDEXEDDB DATABASE IF NOT EXISTS example;');
|
|
90
|
-
libALASQL('ATTACH INDEXEDDB DATABASE example;');
|
|
91
|
-
libALASQL('USE example;');
|
|
92
|
-
fable.ALASQL = libALASQL;
|
|
140
|
+
Meadow schemas define your data model with special column types that enable automatic behavior:
|
|
93
141
|
|
|
142
|
+
| Type | Description |
|
|
143
|
+
|------|-------------|
|
|
144
|
+
| `AutoIdentity` | Auto-increment primary key |
|
|
145
|
+
| `AutoGUID` | Automatically generated GUID |
|
|
146
|
+
| `CreateDate` / `CreateIDUser` | Auto-populated on create |
|
|
147
|
+
| `UpdateDate` / `UpdateIDUser` | Auto-populated on update |
|
|
148
|
+
| `DeleteDate` / `DeleteIDUser` | Auto-populated on delete |
|
|
149
|
+
| `Deleted` | Soft delete flag |
|
|
150
|
+
| `String`, `Text`, `Numeric`, `Decimal`, `Boolean`, `DateTime` | Standard data types |
|
|
94
151
|
|
|
152
|
+
## Providers
|
|
95
153
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
154
|
+
| Provider | Description |
|
|
155
|
+
|----------|-------------|
|
|
156
|
+
| `MySQL` | MySQL/MariaDB via mysql2 |
|
|
157
|
+
| `MSSQL` | Microsoft SQL Server with prepared statements |
|
|
158
|
+
| `SQLite` | SQLite via meadow-connection-sqlite |
|
|
159
|
+
| `ALASQL` | In-browser IndexedDB via ALASQL |
|
|
160
|
+
| `MeadowEndpoints` | REST proxy to remote Meadow API |
|
|
161
|
+
| `None` | Null provider for testing |
|
|
100
162
|
|
|
101
|
-
|
|
102
|
-
var queryDescription = meadow.query.addFilter('customerID', 17);
|
|
163
|
+
## ALASQL Provider Example
|
|
103
164
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
if (error)
|
|
113
|
-
{
|
|
114
|
-
return console.log('Error querying customer data: '+error);
|
|
115
|
-
}
|
|
116
|
-
console.log('Found customer ID '+customer.customerID+' who is named '+customer.name);
|
|
117
|
-
}
|
|
118
|
-
);
|
|
119
|
-
```
|
|
165
|
+
```javascript
|
|
166
|
+
const libFable = require('fable').new();
|
|
167
|
+
const libALASQL = require('alasql');
|
|
168
|
+
|
|
169
|
+
libALASQL('CREATE INDEXEDDB DATABASE IF NOT EXISTS example;');
|
|
170
|
+
libALASQL('ATTACH INDEXEDDB DATABASE example;');
|
|
171
|
+
libALASQL('USE example;');
|
|
172
|
+
libFable.ALASQL = libALASQL;
|
|
120
173
|
|
|
174
|
+
const meadow = require('meadow').new(libFable, 'Customers')
|
|
175
|
+
.setProvider('ALASQL')
|
|
176
|
+
.setDefaultIdentifier('customerID');
|
|
177
|
+
```
|
|
121
178
|
|
|
122
179
|
## MSSQL Docker Image
|
|
123
180
|
|
|
124
|
-
To run
|
|
181
|
+
To run Microsoft SQL Server tests locally:
|
|
125
182
|
|
|
126
|
-
```
|
|
127
|
-
docker run -e "ACCEPT_EULA=Y" -e "MSSQL_SA_PASSWORD=1234567890abc."
|
|
183
|
+
```bash
|
|
184
|
+
docker run -e "ACCEPT_EULA=Y" -e "MSSQL_SA_PASSWORD=1234567890abc." \
|
|
185
|
+
-p 14333:1433 --name meadow-mssql-test --hostname meadowsqltest \
|
|
186
|
+
-d mcr.microsoft.com/mssql/server:2022-latest
|
|
187
|
+
|
|
188
|
+
docker exec meadow-mssql-test sh -c \
|
|
189
|
+
"/opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P '1234567890abc.' -Q 'CREATE DATABASE bookstore;'"
|
|
128
190
|
```
|
|
129
191
|
|
|
130
|
-
|
|
192
|
+
## Testing
|
|
131
193
|
|
|
132
|
-
```
|
|
133
|
-
|
|
194
|
+
```bash
|
|
195
|
+
npm test
|
|
134
196
|
```
|
|
135
197
|
|
|
136
|
-
|
|
198
|
+
## Documentation
|
|
137
199
|
|
|
200
|
+
Detailed documentation is available in the `docs/` folder and can be served locally:
|
|
138
201
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
docker build ./ -t retold/meadow:local
|
|
202
|
+
```bash
|
|
203
|
+
npx docsify-cli serve docs
|
|
142
204
|
```
|
|
143
205
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
206
|
+
## Related Packages
|
|
207
|
+
|
|
208
|
+
- [foxhound](https://github.com/stevenvelozo/foxhound) - Query DSL for SQL generation
|
|
209
|
+
- [stricture](https://github.com/stevenvelozo/stricture) - Schema definition language
|
|
210
|
+
- [meadow-endpoints](https://github.com/stevenvelozo/meadow-endpoints) - Auto-generated REST endpoints
|
|
211
|
+
- [fable](https://github.com/stevenvelozo/fable) - Application services framework
|
|
212
|
+
|
|
213
|
+
## License
|
|
148
214
|
|
|
149
|
-
|
|
215
|
+
MIT
|
|
150
216
|
|
|
151
|
-
|
|
217
|
+
## Contributing
|
|
152
218
|
|
|
153
|
-
|
|
219
|
+
Pull requests are welcome. For details on our code of conduct, contribution process, and testing requirements, see the [Retold Contributing Guide](https://github.com/stevenvelozo/retold/blob/main/docs/contributing.md).
|
package/docs/README.md
ADDED
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
# Meadow
|
|
2
|
+
|
|
3
|
+
> A data access layer providing magic where you want it, programmability where you don't
|
|
4
|
+
|
|
5
|
+
Meadow is a JavaScript data broker that handles repetitive CRUD operations through a consistent, provider-agnostic interface. It doesn't care whether your data lives in MySQL, MSSQL, SQLite, or an in-browser IndexedDB store -- Meadow provides the schema management, query building, and data marshalling while you provide the business logic.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Provider-Agnostic Design** - Pluggable database backends through a consistent interface
|
|
10
|
+
- **Schema-Driven** - Define your data model once, get validation, default objects, and audit tracking for free
|
|
11
|
+
- **FoxHound Query DSL** - Fluent query builder that generates dialect-specific SQL
|
|
12
|
+
- **Automatic Audit Tracking** - Auto-populated create/update/delete timestamps and user stamps
|
|
13
|
+
- **Soft Deletes** - Built-in logical deletion with automatic query filtering
|
|
14
|
+
- **GUID Uniqueness** - Automatic GUID generation and uniqueness enforcement on create
|
|
15
|
+
- **Raw Query Overrides** - Escape hatch for custom SQL when the DSL isn't enough
|
|
16
|
+
- **Fable Integration** - First-class service in the Fable ecosystem with logging and configuration
|
|
17
|
+
|
|
18
|
+
## Quick Start
|
|
19
|
+
|
|
20
|
+
```javascript
|
|
21
|
+
const libFable = require('fable').new();
|
|
22
|
+
const libMeadow = require('meadow');
|
|
23
|
+
|
|
24
|
+
// Create a Meadow DAL for the "Book" entity
|
|
25
|
+
const meadow = libMeadow.new(libFable, 'Book')
|
|
26
|
+
.setProvider('MySQL')
|
|
27
|
+
.setDefaultIdentifier('IDBook')
|
|
28
|
+
.setSchema([
|
|
29
|
+
{ Column: 'IDBook', Type: 'AutoIdentity' },
|
|
30
|
+
{ Column: 'GUIDBook', Type: 'AutoGUID' },
|
|
31
|
+
{ Column: 'Title', Type: 'String', Size: '255' },
|
|
32
|
+
{ Column: 'Author', Type: 'String', Size: '128' },
|
|
33
|
+
{ Column: 'CreateDate', Type: 'CreateDate' },
|
|
34
|
+
{ Column: 'CreatingIDUser', Type: 'CreateIDUser' },
|
|
35
|
+
{ Column: 'UpdateDate', Type: 'UpdateDate' },
|
|
36
|
+
{ Column: 'UpdatingIDUser', Type: 'UpdateIDUser' },
|
|
37
|
+
{ Column: 'DeleteDate', Type: 'DeleteDate' },
|
|
38
|
+
{ Column: 'DeletingIDUser', Type: 'DeleteIDUser' },
|
|
39
|
+
{ Column: 'Deleted', Type: 'Deleted' }
|
|
40
|
+
]);
|
|
41
|
+
|
|
42
|
+
// Read a single record
|
|
43
|
+
meadow.doRead(meadow.query.addFilter('IDBook', 42),
|
|
44
|
+
(pError, pQuery, pBook) =>
|
|
45
|
+
{
|
|
46
|
+
if (pError)
|
|
47
|
+
{
|
|
48
|
+
return console.log('Error reading book:', pError);
|
|
49
|
+
}
|
|
50
|
+
console.log('Found:', pBook.Title, 'by', pBook.Author);
|
|
51
|
+
});
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Installation
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
npm install meadow
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## How It Works
|
|
61
|
+
|
|
62
|
+
Meadow follows the Fable service provider pattern. You create a DAL instance for each entity in your data model, configure its schema and provider, then use the CRUD methods to interact with your database. Meadow handles query generation, data marshalling, schema validation, and audit stamping automatically.
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
Fable (Core)
|
|
66
|
+
└── Meadow (Data Access Layer)
|
|
67
|
+
├── Schema (Column definitions, validation, defaults)
|
|
68
|
+
├── FoxHound Query DSL (Dialect-specific SQL generation)
|
|
69
|
+
├── Behaviors (Create, Read, Reads, Update, Delete, Count, Undelete)
|
|
70
|
+
└── Provider (MySQL, MSSQL, SQLite, ALASQL, MeadowEndpoints, None)
|
|
71
|
+
└── Database Connection
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## CRUD Operations
|
|
75
|
+
|
|
76
|
+
All operations follow an async waterfall pattern with a consistent callback signature.
|
|
77
|
+
|
|
78
|
+
### Create
|
|
79
|
+
|
|
80
|
+
```javascript
|
|
81
|
+
const tmpQuery = meadow.query.addRecord({ Title: 'Dune', Author: 'Frank Herbert' });
|
|
82
|
+
meadow.doCreate(tmpQuery,
|
|
83
|
+
(pError, pCreateQuery, pReadQuery, pRecord) =>
|
|
84
|
+
{
|
|
85
|
+
console.log('Created book with ID:', pRecord.IDBook);
|
|
86
|
+
});
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Read
|
|
90
|
+
|
|
91
|
+
```javascript
|
|
92
|
+
// Single record
|
|
93
|
+
meadow.doRead(meadow.query.addFilter('IDBook', 1),
|
|
94
|
+
(pError, pQuery, pRecord) =>
|
|
95
|
+
{
|
|
96
|
+
console.log('Book:', pRecord.Title);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// Multiple records
|
|
100
|
+
meadow.doReads(meadow.query.setCap(25).setBegin(0),
|
|
101
|
+
(pError, pQuery, pRecords) =>
|
|
102
|
+
{
|
|
103
|
+
console.log('Found', pRecords.length, 'books');
|
|
104
|
+
});
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Update
|
|
108
|
+
|
|
109
|
+
```javascript
|
|
110
|
+
const tmpQuery = meadow.query
|
|
111
|
+
.addFilter('IDBook', 1)
|
|
112
|
+
.addRecord({ Title: 'Updated Title' });
|
|
113
|
+
meadow.doUpdate(tmpQuery,
|
|
114
|
+
(pError, pUpdateQuery, pReadQuery, pRecord) =>
|
|
115
|
+
{
|
|
116
|
+
console.log('Updated:', pRecord.Title);
|
|
117
|
+
});
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Delete
|
|
121
|
+
|
|
122
|
+
```javascript
|
|
123
|
+
meadow.doDelete(meadow.query.addFilter('IDBook', 1),
|
|
124
|
+
(pError, pQuery, pResult) =>
|
|
125
|
+
{
|
|
126
|
+
console.log('Deleted:', pResult, 'record(s)');
|
|
127
|
+
});
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Count
|
|
131
|
+
|
|
132
|
+
```javascript
|
|
133
|
+
meadow.doCount(meadow.query,
|
|
134
|
+
(pError, pQuery, pCount) =>
|
|
135
|
+
{
|
|
136
|
+
console.log('Total books:', pCount);
|
|
137
|
+
});
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Schema
|
|
141
|
+
|
|
142
|
+
Meadow schemas define your data model with special column types that enable automatic behavior.
|
|
143
|
+
|
|
144
|
+
### Column Types
|
|
145
|
+
|
|
146
|
+
| Type | Description |
|
|
147
|
+
|------|-------------|
|
|
148
|
+
| `AutoIdentity` | Auto-increment primary key |
|
|
149
|
+
| `AutoGUID` | Automatically generated GUID |
|
|
150
|
+
| `CreateDate` | Auto-populated timestamp on create |
|
|
151
|
+
| `CreateIDUser` | Auto-populated user ID on create |
|
|
152
|
+
| `UpdateDate` | Auto-populated timestamp on update |
|
|
153
|
+
| `UpdateIDUser` | Auto-populated user ID on update |
|
|
154
|
+
| `DeleteDate` | Auto-populated timestamp on delete |
|
|
155
|
+
| `DeleteIDUser` | Auto-populated user ID on delete |
|
|
156
|
+
| `Deleted` | Soft delete flag (enables logical deletion) |
|
|
157
|
+
| `String` | String field with optional `Size` |
|
|
158
|
+
| `Text` | Long text field |
|
|
159
|
+
| `Numeric` | Integer field |
|
|
160
|
+
| `Decimal` | Decimal field with `Size` as `"precision,scale"` |
|
|
161
|
+
| `Boolean` | Boolean field |
|
|
162
|
+
| `DateTime` | Date/time field |
|
|
163
|
+
|
|
164
|
+
### JSON Schema Validation
|
|
165
|
+
|
|
166
|
+
Meadow also supports JSON Schema (v4) for object validation:
|
|
167
|
+
|
|
168
|
+
```javascript
|
|
169
|
+
meadow.setJsonSchema({
|
|
170
|
+
title: 'Book',
|
|
171
|
+
type: 'object',
|
|
172
|
+
properties:
|
|
173
|
+
{
|
|
174
|
+
IDBook: { type: 'integer' },
|
|
175
|
+
Title: { type: 'string' },
|
|
176
|
+
Author: { type: 'string' }
|
|
177
|
+
},
|
|
178
|
+
required: ['Title']
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
const result = meadow.schemaFull.validateObject({ Title: 'Dune' });
|
|
182
|
+
// { Valid: true, Errors: [] }
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## Configuration
|
|
186
|
+
|
|
187
|
+
Meadow reads database configuration from the Fable settings object:
|
|
188
|
+
|
|
189
|
+
```json
|
|
190
|
+
{
|
|
191
|
+
"Product": "MyApp",
|
|
192
|
+
"ProductVersion": "1.0.0",
|
|
193
|
+
"MySQL": {
|
|
194
|
+
"Server": "localhost",
|
|
195
|
+
"Port": 3306,
|
|
196
|
+
"User": "root",
|
|
197
|
+
"Password": "",
|
|
198
|
+
"Database": "myapp",
|
|
199
|
+
"ConnectionPoolLimit": 20
|
|
200
|
+
},
|
|
201
|
+
"QueryThresholdWarnTime": 200
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
| Setting | Type | Default | Description |
|
|
206
|
+
|---------|------|---------|-------------|
|
|
207
|
+
| `MySQL` | object | - | MySQL connection settings |
|
|
208
|
+
| `QueryThresholdWarnTime` | number | `200` | Slow query warning threshold (ms) |
|
|
209
|
+
| `MeadowProvider` | string | - | Default provider name |
|
|
210
|
+
| `MeadowRoleNames` | array | `['Unauthenticated', 'User', 'Manager', 'Director', 'Executive', 'Administrator']` | Role name mapping |
|
|
211
|
+
|
|
212
|
+
## Providers
|
|
213
|
+
|
|
214
|
+
Meadow supports multiple database backends through its provider system.
|
|
215
|
+
|
|
216
|
+
| Provider | Description | Connection |
|
|
217
|
+
|----------|-------------|------------|
|
|
218
|
+
| `MySQL` | MySQL/MariaDB via mysql2 | `fable.MeadowMySQLConnectionPool` or `fable.MeadowMySQLProvider` |
|
|
219
|
+
| `MSSQL` | Microsoft SQL Server | `fable.MeadowMSSQLProvider` |
|
|
220
|
+
| `SQLite` | SQLite database | Via meadow-connection-sqlite |
|
|
221
|
+
| `ALASQL` | In-browser IndexedDB | `fable.ALASQL` |
|
|
222
|
+
| `MeadowEndpoints` | REST proxy to remote Meadow API | Via `MeadowEndpoints` settings |
|
|
223
|
+
| `None` | Null provider for testing | No connection required |
|
|
224
|
+
|
|
225
|
+
## Raw Query Overrides
|
|
226
|
+
|
|
227
|
+
For complex queries that exceed the DSL capabilities:
|
|
228
|
+
|
|
229
|
+
```javascript
|
|
230
|
+
// Set a custom query for Read operations
|
|
231
|
+
meadow.rawQueries.setQuery('Read',
|
|
232
|
+
'SELECT b.*, a.Name AS AuthorName FROM Book b JOIN Author a ON b.IDAuthor = a.IDAuthor WHERE b.IDBook = :IDBook');
|
|
233
|
+
|
|
234
|
+
// Load from file
|
|
235
|
+
meadow.rawQueries.loadQuery('CustomReport', './queries/book-report.sql');
|
|
236
|
+
|
|
237
|
+
// Check and use
|
|
238
|
+
if (meadow.rawQueries.checkQuery('CustomReport'))
|
|
239
|
+
{
|
|
240
|
+
const sql = meadow.rawQueries.getQuery('CustomReport');
|
|
241
|
+
}
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
## Testing
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
npm test
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
## Documentation
|
|
251
|
+
|
|
252
|
+
- [Architecture](architecture.md) - System architecture and design patterns
|
|
253
|
+
- [Schema](schema/README.md) - Column definitions, JSON Schema validation, default objects, authorization
|
|
254
|
+
- [Query DSL](query-dsl.md) - FoxHound query building
|
|
255
|
+
- [Query Operations](query/README.md) - Query object overview and CRUD operations
|
|
256
|
+
- [Create](query/create.md) - Insert new records
|
|
257
|
+
- [Read](query/read.md) - Retrieve single and multiple records
|
|
258
|
+
- [Update](query/update.md) - Modify existing records
|
|
259
|
+
- [Delete](query/delete.md) - Soft delete and undelete records
|
|
260
|
+
- [Count](query/count.md) - Count records matching criteria
|
|
261
|
+
- [Providers](providers/README.md) - Database provider system
|
|
262
|
+
- [MySQL](providers/mysql.md) - Connection pooling, named placeholders
|
|
263
|
+
- [MSSQL](providers/mssql.md) - Prepared statements, type mapping
|
|
264
|
+
- [SQLite](providers/sqlite.md) - Embedded database
|
|
265
|
+
- [ALASQL](providers/alasql.md) - In-memory SQL, browser support
|
|
266
|
+
|
|
267
|
+
## Related Packages
|
|
268
|
+
|
|
269
|
+
- [foxhound](https://github.com/stevenvelozo/foxhound) - Query DSL for SQL generation
|
|
270
|
+
- [stricture](https://github.com/stevenvelozo/stricture) - Schema definition language
|
|
271
|
+
- [meadow-endpoints](https://github.com/stevenvelozo/meadow-endpoints) - Automatic REST endpoint generation
|
|
272
|
+
- [meadow-connection-mysql](https://github.com/stevenvelozo/meadow-connection-mysql) - MySQL connection provider
|
|
273
|
+
- [meadow-connection-mssql](https://github.com/stevenvelozo/meadow-connection-mssql) - MSSQL connection provider
|
|
274
|
+
- [meadow-connection-sqlite](https://github.com/stevenvelozo/meadow-connection-sqlite) - SQLite connection provider
|
|
275
|
+
- [fable](https://github.com/stevenvelozo/fable) - Service provider framework
|
|
276
|
+
- [orator](https://github.com/stevenvelozo/orator) - API server abstraction
|