meadow-connection-sqlite 1.0.10
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/LICENSE +21 -0
- package/README.md +174 -0
- package/debug/Harness.js +139 -0
- package/debug/Pipeline-Test.js +143 -0
- package/docs/.nojekyll +0 -0
- package/docs/README.md +153 -0
- package/docs/_sidebar.md +20 -0
- package/docs/_topbar.md +5 -0
- package/docs/api.md +256 -0
- package/docs/cover.md +15 -0
- package/docs/css/docuserve.css +73 -0
- package/docs/examples-pipeline.md +331 -0
- package/docs/index.html +39 -0
- package/docs/retold-catalog.json +24 -0
- package/docs/retold-keyword-index.json +19 -0
- package/package.json +52 -0
- package/source/Meadow-Connection-SQLite.js +246 -0
- package/test/SQLite_tests.js +89 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 Steven Velozo
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# Meadow Connection SQLite
|
|
2
|
+
|
|
3
|
+
A SQLite database connection provider for the Meadow ORM. Wraps [better-sqlite3](https://github.com/WiseLibs/better-sqlite3) as a Fable service, providing file-based database connections with WAL journal mode and DDL generation from Meadow table schemas.
|
|
4
|
+
|
|
5
|
+
[](https://github.com/stevenvelozo/meadow-connection-sqlite/actions)
|
|
6
|
+
[](https://badge.fury.io/js/meadow-connection-sqlite)
|
|
7
|
+
[](https://opensource.org/licenses/MIT)
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- **Better-SQLite3 Wrapper** - Synchronous, high-performance SQLite access via [better-sqlite3](https://github.com/WiseLibs/better-sqlite3)
|
|
14
|
+
- **Fable Service Provider** - Registers with a Fable instance for dependency injection, logging, and configuration
|
|
15
|
+
- **WAL Journal Mode** - Automatically enables Write-Ahead Logging for performance on connect
|
|
16
|
+
- **Schema-Driven DDL** - Generates `CREATE TABLE` statements from Meadow table schemas with support for ID, GUID, ForeignKey, Numeric, Decimal, String, Text, DateTime, and Boolean column types
|
|
17
|
+
- **Connection Safety** - Guards against double-connect and missing file path errors with descriptive logging
|
|
18
|
+
- **Direct Database Access** - Exposes the underlying `better-sqlite3` database instance via `db` getter
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install meadow-connection-sqlite
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Quick Start
|
|
27
|
+
|
|
28
|
+
```javascript
|
|
29
|
+
const libFable = require('fable');
|
|
30
|
+
const MeadowConnectionSQLite = require('meadow-connection-sqlite');
|
|
31
|
+
|
|
32
|
+
let fable = new libFable(
|
|
33
|
+
{
|
|
34
|
+
SQLite:
|
|
35
|
+
{
|
|
36
|
+
SQLiteFilePath: './my-database.sqlite'
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
let connection = fable.instantiateServiceProvider('MeadowConnectionSQLite',
|
|
41
|
+
{}, MeadowConnectionSQLite);
|
|
42
|
+
|
|
43
|
+
connection.connectAsync((pError, pDatabase) =>
|
|
44
|
+
{
|
|
45
|
+
if (pError)
|
|
46
|
+
{
|
|
47
|
+
console.error('Connection failed:', pError);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Use the better-sqlite3 database directly
|
|
52
|
+
let stmt = connection.db.prepare('SELECT * FROM Users WHERE id = ?');
|
|
53
|
+
let user = stmt.get(42);
|
|
54
|
+
});
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Configuration
|
|
58
|
+
|
|
59
|
+
The SQLite file path can be provided through Fable settings or the service provider options:
|
|
60
|
+
|
|
61
|
+
### Via Fable Settings
|
|
62
|
+
|
|
63
|
+
```javascript
|
|
64
|
+
let fable = new libFable(
|
|
65
|
+
{
|
|
66
|
+
SQLite:
|
|
67
|
+
{
|
|
68
|
+
SQLiteFilePath: './data/app.sqlite'
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Via Provider Options
|
|
74
|
+
|
|
75
|
+
```javascript
|
|
76
|
+
let connection = fable.instantiateServiceProvider('MeadowConnectionSQLite',
|
|
77
|
+
{
|
|
78
|
+
SQLiteFilePath: './data/app.sqlite'
|
|
79
|
+
}, MeadowConnectionSQLite);
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Any additional options are passed through to the `better-sqlite3` constructor.
|
|
83
|
+
|
|
84
|
+
## API
|
|
85
|
+
|
|
86
|
+
### `connectAsync(fCallback)`
|
|
87
|
+
|
|
88
|
+
Open the SQLite database file and enable WAL journal mode.
|
|
89
|
+
|
|
90
|
+
| Parameter | Type | Description |
|
|
91
|
+
|-----------|------|-------------|
|
|
92
|
+
| `fCallback` | `Function` | Callback receiving `(error, database)` |
|
|
93
|
+
|
|
94
|
+
### `connect()`
|
|
95
|
+
|
|
96
|
+
Synchronous convenience wrapper for `connectAsync` (no callback, logs a warning).
|
|
97
|
+
|
|
98
|
+
### `db` (getter)
|
|
99
|
+
|
|
100
|
+
Returns the underlying `better-sqlite3` database instance for direct query access.
|
|
101
|
+
|
|
102
|
+
### `SQLite` (getter)
|
|
103
|
+
|
|
104
|
+
Returns the `better-sqlite3` library module.
|
|
105
|
+
|
|
106
|
+
### `connected` (property)
|
|
107
|
+
|
|
108
|
+
Boolean indicating whether the database connection is open.
|
|
109
|
+
|
|
110
|
+
### `generateCreateTableStatement(pMeadowTableSchema)`
|
|
111
|
+
|
|
112
|
+
Generate a `CREATE TABLE` SQL statement from a Meadow table schema object.
|
|
113
|
+
|
|
114
|
+
| Parameter | Type | Description |
|
|
115
|
+
|-----------|------|-------------|
|
|
116
|
+
| `pMeadowTableSchema` | `Object` | Meadow table schema with `TableName` and `Columns` array |
|
|
117
|
+
|
|
118
|
+
### `createTable(pMeadowTableSchema, fCallback)`
|
|
119
|
+
|
|
120
|
+
Execute a `CREATE TABLE` statement against the connected database.
|
|
121
|
+
|
|
122
|
+
### `createTables(pMeadowSchema, fCallback)`
|
|
123
|
+
|
|
124
|
+
Create all tables defined in a Meadow schema object (iterates `pMeadowSchema.Tables` sequentially).
|
|
125
|
+
|
|
126
|
+
### `generateDropTableStatement(pTableName)`
|
|
127
|
+
|
|
128
|
+
Generate a `DROP TABLE IF EXISTS` SQL statement for the given table name.
|
|
129
|
+
|
|
130
|
+
## Column Type Mapping
|
|
131
|
+
|
|
132
|
+
| Meadow Type | SQLite Column |
|
|
133
|
+
|-------------|---------------|
|
|
134
|
+
| `ID` | `INT NOT NULL IDENTITY PRIMARY KEY` |
|
|
135
|
+
| `GUID` | `VARCHAR(254)` with default GUID |
|
|
136
|
+
| `ForeignKey` | `INT UNSIGNED NOT NULL DEFAULT 0` |
|
|
137
|
+
| `Numeric` | `INT NOT NULL DEFAULT 0` |
|
|
138
|
+
| `Decimal` | `DECIMAL(size)` |
|
|
139
|
+
| `String` | `VARCHAR(size) DEFAULT ''` |
|
|
140
|
+
| `Text` | `TEXT` |
|
|
141
|
+
| `DateTime` | `DATETIME` |
|
|
142
|
+
| `Boolean` | `TINYINT DEFAULT 0` |
|
|
143
|
+
|
|
144
|
+
## Part of the Retold Framework
|
|
145
|
+
|
|
146
|
+
Meadow Connection SQLite is a database connector for the Meadow data access layer:
|
|
147
|
+
|
|
148
|
+
- [meadow](https://github.com/stevenvelozo/meadow) - ORM and data access framework
|
|
149
|
+
- [foxhound](https://github.com/stevenvelozo/foxhound) - Query DSL used by Meadow
|
|
150
|
+
- [stricture](https://github.com/stevenvelozo/stricture) - Schema definition tool
|
|
151
|
+
- [meadow-endpoints](https://github.com/stevenvelozo/meadow-endpoints) - RESTful endpoint generation
|
|
152
|
+
- [fable](https://github.com/stevenvelozo/fable) - Application services framework
|
|
153
|
+
|
|
154
|
+
## Testing
|
|
155
|
+
|
|
156
|
+
Run the test suite:
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
npm test
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Run with coverage:
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
npm run coverage
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## License
|
|
169
|
+
|
|
170
|
+
MIT - See [LICENSE](LICENSE) for details.
|
|
171
|
+
|
|
172
|
+
## Author
|
|
173
|
+
|
|
174
|
+
Steven Velozo - [steven@velozo.com](mailto:steven@velozo.com)
|
package/debug/Harness.js
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Exercise the create table functionality of the SQLite provider
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* @author <steven@velozo.com>
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const libFable = require('fable');
|
|
9
|
+
let _Fable = new libFable(
|
|
10
|
+
{
|
|
11
|
+
"Product": "MeadowSQLiteTestBookstore",
|
|
12
|
+
"ProductVersion": "1.0.0",
|
|
13
|
+
|
|
14
|
+
"UUID":
|
|
15
|
+
{
|
|
16
|
+
"DataCenter": 0,
|
|
17
|
+
"Worker": 0
|
|
18
|
+
},
|
|
19
|
+
"LogStreams":
|
|
20
|
+
[
|
|
21
|
+
{
|
|
22
|
+
"streamtype": "console"
|
|
23
|
+
}
|
|
24
|
+
],
|
|
25
|
+
|
|
26
|
+
"SQLite":
|
|
27
|
+
{
|
|
28
|
+
"server": "127.0.0.1",
|
|
29
|
+
"port": 3306,
|
|
30
|
+
"user": "sa",
|
|
31
|
+
"password": "1234567890abc.",
|
|
32
|
+
"database": "bookstore",
|
|
33
|
+
"ConnectionPoolLimit": 20
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
_Fable.log.info("Application is starting up...");
|
|
38
|
+
|
|
39
|
+
const libMeadowConnectionSQLite = require('../source/Meadow-Connection-SQLite.js');
|
|
40
|
+
_Fable.serviceManager.addServiceType('MeadowSQLiteProvider', libMeadowConnectionSQLite);
|
|
41
|
+
_Fable.serviceManager.instantiateServiceProvider('MeadowSQLiteProvider',
|
|
42
|
+
{
|
|
43
|
+
// This makes the sync service still able to sync IDs.
|
|
44
|
+
AllowIdentityInsert: true
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
_Fable.log.info("...Creating SQL Connection pools at " + _Fable.settings.SQLite.server + "...");
|
|
48
|
+
|
|
49
|
+
_Fable.MeadowSQLiteProvider.connectAsync(
|
|
50
|
+
(pError, pConnectionPool) =>
|
|
51
|
+
{
|
|
52
|
+
if (pError)
|
|
53
|
+
{
|
|
54
|
+
_Fable.log.error(`Error connecting to MS SQL Database: ${pError}`);
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const tmpTestModel = require('../retold-harness/model/json_schema/BookStore-Extended.json');
|
|
59
|
+
|
|
60
|
+
_Fable.log.info('Connection complete!');
|
|
61
|
+
_Fable.MeadowSQLiteProvider.createTables(tmpTestModel,
|
|
62
|
+
(pCreateTablesError) =>
|
|
63
|
+
{
|
|
64
|
+
_Fable.log.info('Tables created successfully');
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
// const libFable = require('fable');
|
|
70
|
+
// const libMeadowConnectionSQLite = require('../source/Meadow-Connection-SQLite.js');
|
|
71
|
+
|
|
72
|
+
// let tmpTestModel = require('../retold-harness/model/json_schema/BookStore-Extended.json');
|
|
73
|
+
|
|
74
|
+
// let _Fable = new libFable(
|
|
75
|
+
// {
|
|
76
|
+
// "Product": "MeadowSQLiteTestBookstore",
|
|
77
|
+
// "ProductVersion": "1.0.0",
|
|
78
|
+
|
|
79
|
+
// "UUID":
|
|
80
|
+
// {
|
|
81
|
+
// "DataCenter": 0,
|
|
82
|
+
// "Worker": 0
|
|
83
|
+
// },
|
|
84
|
+
// "LogStreams":
|
|
85
|
+
// [
|
|
86
|
+
// {
|
|
87
|
+
// "streamtype": "console"
|
|
88
|
+
// }
|
|
89
|
+
// ],
|
|
90
|
+
|
|
91
|
+
// "SQLite":
|
|
92
|
+
// {
|
|
93
|
+
// "server": "127.0.0.1",
|
|
94
|
+
// "port": 3306,
|
|
95
|
+
// "user": "sa",
|
|
96
|
+
// "password": "1234567890abc.",
|
|
97
|
+
// "database": "bookstore",
|
|
98
|
+
// "ConnectionPoolLimit": 20
|
|
99
|
+
// }
|
|
100
|
+
// });
|
|
101
|
+
|
|
102
|
+
// _Fable.serviceManager.addServiceType('MeadowSQLiteProvider', libMeadowConnectionSQLite);
|
|
103
|
+
|
|
104
|
+
// _Fable.serviceManager.instantiateServiceProvider('MeadowSQLiteProvider');
|
|
105
|
+
|
|
106
|
+
// _Fable.MeadowSQLiteProvider.connectAsync(
|
|
107
|
+
// (pError, pConnectionPool) =>
|
|
108
|
+
// {
|
|
109
|
+
// if (pError)
|
|
110
|
+
// {
|
|
111
|
+
// _Fable.log.error(`Error connecting to MS SQL Database: ${pError}`);
|
|
112
|
+
// return false;
|
|
113
|
+
// }
|
|
114
|
+
|
|
115
|
+
// _Fable.log.info('Connection complete!');
|
|
116
|
+
// _Fable.MeadowSQLiteProvider.createTables(tmpTestModel,
|
|
117
|
+
// (pCreateTablesError) =>
|
|
118
|
+
// {
|
|
119
|
+
// _Fable.log.info('Tables created successfully');
|
|
120
|
+
// let tmpPreparedStatement = _Fable.MeadowSQLiteProvider.preparedStatement;
|
|
121
|
+
// tmpPreparedStatement.input('param', _Fable.MeadowSQLiteProvider.SQLite.Int);
|
|
122
|
+
// tmpPreparedStatement.prepare('SELECT * FROM Book WHERE IDBook < @param',
|
|
123
|
+
// (pPrepareError) =>
|
|
124
|
+
// {
|
|
125
|
+
// tmpPreparedStatement.execute({ param: 12345 },
|
|
126
|
+
// (pPreparedExecutionError, pPreparedResult) =>
|
|
127
|
+
// {
|
|
128
|
+
// _Fable.log.info(`Prepared statement returned...`, pPreparedResult);
|
|
129
|
+
// // release the connection after queries are executed
|
|
130
|
+
// tmpPreparedStatement.unprepare(
|
|
131
|
+
// (pPreparedStatementUnprepareError) =>
|
|
132
|
+
// {
|
|
133
|
+
// _Fable.log.trace(`Prepared statement unprepared.`);
|
|
134
|
+
// });
|
|
135
|
+
// })
|
|
136
|
+
// });
|
|
137
|
+
// });
|
|
138
|
+
// }
|
|
139
|
+
// );
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Full pipeline test for meadow-connection-sqlite:
|
|
3
|
+
Connect → Create Table → Insert → Read → Update → Delete → Transactions → Close
|
|
4
|
+
*/
|
|
5
|
+
const libFable = require('fable');
|
|
6
|
+
const libMeadowConnectionSQLite = require('../source/Meadow-Connection-SQLite.js');
|
|
7
|
+
const libFS = require('fs');
|
|
8
|
+
|
|
9
|
+
// Clean up any leftover test database
|
|
10
|
+
const DB_PATH = './dist/PipelineTest.db';
|
|
11
|
+
if (libFS.existsSync(DB_PATH))
|
|
12
|
+
{
|
|
13
|
+
libFS.unlinkSync(DB_PATH);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
let _Fable = new libFable(
|
|
17
|
+
{
|
|
18
|
+
"Product": "MeadowSQLitePipelineTest",
|
|
19
|
+
"ProductVersion": "1.0.0",
|
|
20
|
+
"UUID": { "DataCenter": 0, "Worker": 0 },
|
|
21
|
+
"LogStreams": [{ "streamtype": "console" }],
|
|
22
|
+
"SQLite": { "SQLiteFilePath": DB_PATH }
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
// 1. Register and instantiate the provider
|
|
26
|
+
_Fable.serviceManager.addServiceType('MeadowSQLiteProvider', libMeadowConnectionSQLite);
|
|
27
|
+
_Fable.serviceManager.instantiateServiceProvider('MeadowSQLiteProvider');
|
|
28
|
+
|
|
29
|
+
_Fable.log.info('--- Step 1: Connect ---');
|
|
30
|
+
_Fable.MeadowSQLiteProvider.connectAsync(
|
|
31
|
+
(pError, pDatabase) =>
|
|
32
|
+
{
|
|
33
|
+
if (pError)
|
|
34
|
+
{
|
|
35
|
+
_Fable.log.error(`Connection failed: ${pError}`);
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
_Fable.log.info(`Connected: ${_Fable.MeadowSQLiteProvider.connected}`);
|
|
39
|
+
|
|
40
|
+
let tmpDB = _Fable.MeadowSQLiteProvider.db;
|
|
41
|
+
|
|
42
|
+
// 2. Create a table using SQLite-compatible SQL
|
|
43
|
+
_Fable.log.info('--- Step 2: Create Table ---');
|
|
44
|
+
tmpDB.exec(`
|
|
45
|
+
CREATE TABLE IF NOT EXISTS Book (
|
|
46
|
+
IDBook INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
47
|
+
GUIDBook TEXT DEFAULT '00000000-0000-0000-0000-000000000000',
|
|
48
|
+
Title TEXT DEFAULT '',
|
|
49
|
+
Author TEXT DEFAULT '',
|
|
50
|
+
YearPublished INTEGER DEFAULT 0,
|
|
51
|
+
Price REAL DEFAULT 0.0,
|
|
52
|
+
CreateDate TEXT DEFAULT (datetime('now')),
|
|
53
|
+
UpdateDate TEXT DEFAULT (datetime('now'))
|
|
54
|
+
)
|
|
55
|
+
`);
|
|
56
|
+
_Fable.log.info('Table "Book" created.');
|
|
57
|
+
|
|
58
|
+
// 3. Insert rows
|
|
59
|
+
_Fable.log.info('--- Step 3: Insert ---');
|
|
60
|
+
let tmpInsert = tmpDB.prepare(
|
|
61
|
+
`INSERT INTO Book (GUIDBook, Title, Author, YearPublished, Price) VALUES (?, ?, ?, ?, ?)`
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
let tmpResult1 = tmpInsert.run(_Fable.fable.getUUID(), 'The Hobbit', 'J.R.R. Tolkien', 1937, 12.99);
|
|
65
|
+
_Fable.log.info(`Inserted IDBook=${tmpResult1.lastInsertRowid}`);
|
|
66
|
+
|
|
67
|
+
let tmpResult2 = tmpInsert.run(_Fable.fable.getUUID(), 'Dune', 'Frank Herbert', 1965, 14.99);
|
|
68
|
+
_Fable.log.info(`Inserted IDBook=${tmpResult2.lastInsertRowid}`);
|
|
69
|
+
|
|
70
|
+
let tmpResult3 = tmpInsert.run(_Fable.fable.getUUID(), 'Neuromancer', 'William Gibson', 1984, 11.50);
|
|
71
|
+
_Fable.log.info(`Inserted IDBook=${tmpResult3.lastInsertRowid}`);
|
|
72
|
+
|
|
73
|
+
// 4. Read all rows
|
|
74
|
+
_Fable.log.info('--- Step 4: Read All ---');
|
|
75
|
+
let tmpAllBooks = tmpDB.prepare(`SELECT * FROM Book`).all();
|
|
76
|
+
_Fable.log.info(`Found ${tmpAllBooks.length} books:`);
|
|
77
|
+
for (let i = 0; i < tmpAllBooks.length; i++)
|
|
78
|
+
{
|
|
79
|
+
_Fable.log.info(` [${tmpAllBooks[i].IDBook}] "${tmpAllBooks[i].Title}" by ${tmpAllBooks[i].Author} (${tmpAllBooks[i].YearPublished}) - $${tmpAllBooks[i].Price}`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// 5. Read single row
|
|
83
|
+
_Fable.log.info('--- Step 5: Read One ---');
|
|
84
|
+
let tmpOneBook = tmpDB.prepare(`SELECT * FROM Book WHERE IDBook = ?`).get(2);
|
|
85
|
+
_Fable.log.info(`Single book: "${tmpOneBook.Title}" by ${tmpOneBook.Author}`);
|
|
86
|
+
|
|
87
|
+
// 6. Update
|
|
88
|
+
_Fable.log.info('--- Step 6: Update ---');
|
|
89
|
+
let tmpUpdate = tmpDB.prepare(`UPDATE Book SET Price = ?, UpdateDate = datetime('now') WHERE IDBook = ?`);
|
|
90
|
+
let tmpUpdateResult = tmpUpdate.run(16.99, 2);
|
|
91
|
+
_Fable.log.info(`Updated ${tmpUpdateResult.changes} row(s) — Dune price is now $16.99`);
|
|
92
|
+
|
|
93
|
+
let tmpUpdatedBook = tmpDB.prepare(`SELECT * FROM Book WHERE IDBook = ?`).get(2);
|
|
94
|
+
_Fable.log.info(`Verified: "${tmpUpdatedBook.Title}" price = $${tmpUpdatedBook.Price}`);
|
|
95
|
+
|
|
96
|
+
// 7. Delete
|
|
97
|
+
_Fable.log.info('--- Step 7: Delete ---');
|
|
98
|
+
let tmpDelete = tmpDB.prepare(`DELETE FROM Book WHERE IDBook = ?`);
|
|
99
|
+
let tmpDeleteResult = tmpDelete.run(3);
|
|
100
|
+
_Fable.log.info(`Deleted ${tmpDeleteResult.changes} row(s) — Neuromancer removed`);
|
|
101
|
+
|
|
102
|
+
let tmpRemainingBooks = tmpDB.prepare(`SELECT * FROM Book`).all();
|
|
103
|
+
_Fable.log.info(`Remaining books: ${tmpRemainingBooks.length}`);
|
|
104
|
+
|
|
105
|
+
// 8. Transaction
|
|
106
|
+
_Fable.log.info('--- Step 8: Transaction ---');
|
|
107
|
+
let tmpBulkInsert = tmpDB.transaction(
|
|
108
|
+
(pBooks) =>
|
|
109
|
+
{
|
|
110
|
+
let tmpBulkStmt = tmpDB.prepare(
|
|
111
|
+
`INSERT INTO Book (GUIDBook, Title, Author, YearPublished, Price) VALUES (?, ?, ?, ?, ?)`
|
|
112
|
+
);
|
|
113
|
+
for (let tmpBook of pBooks)
|
|
114
|
+
{
|
|
115
|
+
tmpBulkStmt.run(tmpBook.GUID, tmpBook.Title, tmpBook.Author, tmpBook.Year, tmpBook.Price);
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
tmpBulkInsert(
|
|
120
|
+
[
|
|
121
|
+
{ GUID: _Fable.fable.getUUID(), Title: 'Snow Crash', Author: 'Neal Stephenson', Year: 1992, Price: 13.99 },
|
|
122
|
+
{ GUID: _Fable.fable.getUUID(), Title: 'Hyperion', Author: 'Dan Simmons', Year: 1989, Price: 15.50 },
|
|
123
|
+
{ GUID: _Fable.fable.getUUID(), Title: 'Foundation', Author: 'Isaac Asimov', Year: 1951, Price: 10.99 }
|
|
124
|
+
]);
|
|
125
|
+
_Fable.log.info('Bulk insert (3 books) committed in a single transaction.');
|
|
126
|
+
|
|
127
|
+
let tmpFinalBooks = tmpDB.prepare(`SELECT * FROM Book ORDER BY IDBook`).all();
|
|
128
|
+
_Fable.log.info(`Final book count: ${tmpFinalBooks.length}`);
|
|
129
|
+
for (let i = 0; i < tmpFinalBooks.length; i++)
|
|
130
|
+
{
|
|
131
|
+
_Fable.log.info(` [${tmpFinalBooks[i].IDBook}] "${tmpFinalBooks[i].Title}" by ${tmpFinalBooks[i].Author} ($${tmpFinalBooks[i].Price})`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// 9. Aggregate query
|
|
135
|
+
_Fable.log.info('--- Step 9: Aggregates ---');
|
|
136
|
+
let tmpStats = tmpDB.prepare(`SELECT COUNT(*) as BookCount, AVG(Price) as AvgPrice, SUM(Price) as TotalValue FROM Book`).get();
|
|
137
|
+
_Fable.log.info(`Books: ${tmpStats.BookCount}, Average price: $${tmpStats.AvgPrice.toFixed(2)}, Total value: $${tmpStats.TotalValue.toFixed(2)}`);
|
|
138
|
+
|
|
139
|
+
// 10. Close
|
|
140
|
+
_Fable.log.info('--- Step 10: Close ---');
|
|
141
|
+
tmpDB.close();
|
|
142
|
+
_Fable.log.info('Database closed. Pipeline test complete!');
|
|
143
|
+
});
|
package/docs/.nojekyll
ADDED
|
File without changes
|
package/docs/README.md
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# Meadow Connection SQLite
|
|
2
|
+
|
|
3
|
+
A Fable service provider that connects applications to SQLite databases. Wraps [better-sqlite3](https://github.com/WiseLibs/better-sqlite3) and integrates with the Meadow data layer through Fable's dependency injection system.
|
|
4
|
+
|
|
5
|
+
SQLite databases are single files that require no server process. This makes them ideal for local development, embedded applications, desktop tools, CLI utilities, test suites, and any scenario where a full MySQL or MSSQL server is unnecessary overhead.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install meadow-connection-sqlite
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Requires Node.js. The `better-sqlite3` dependency compiles a native addon at install time — a C compiler toolchain must be available on the host.
|
|
14
|
+
|
|
15
|
+
## Quick Start
|
|
16
|
+
|
|
17
|
+
### 1. Configure Fable
|
|
18
|
+
|
|
19
|
+
Add an `SQLite` section to your Fable configuration with the path to the database file. The file is created automatically if it does not exist:
|
|
20
|
+
|
|
21
|
+
```javascript
|
|
22
|
+
const libFable = require('fable');
|
|
23
|
+
|
|
24
|
+
let _Fable = new libFable(
|
|
25
|
+
{
|
|
26
|
+
"Product": "MyApp",
|
|
27
|
+
"SQLite":
|
|
28
|
+
{
|
|
29
|
+
"SQLiteFilePath": "./data/myapp.db"
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### 2. Register the Service
|
|
35
|
+
|
|
36
|
+
```javascript
|
|
37
|
+
const libMeadowConnectionSQLite = require('meadow-connection-sqlite');
|
|
38
|
+
|
|
39
|
+
_Fable.serviceManager.addServiceType('MeadowSQLiteProvider', libMeadowConnectionSQLite);
|
|
40
|
+
_Fable.serviceManager.instantiateServiceProvider('MeadowSQLiteProvider');
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
After instantiation the provider is available at `_Fable.MeadowSQLiteProvider`.
|
|
44
|
+
|
|
45
|
+
### 3. Connect
|
|
46
|
+
|
|
47
|
+
```javascript
|
|
48
|
+
_Fable.MeadowSQLiteProvider.connectAsync(
|
|
49
|
+
(pError, pDatabase) =>
|
|
50
|
+
{
|
|
51
|
+
if (pError)
|
|
52
|
+
{
|
|
53
|
+
_Fable.log.error(`Connection failed: ${pError}`);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Connection is ready — WAL journaling is enabled automatically
|
|
58
|
+
_Fable.log.info('SQLite connected!');
|
|
59
|
+
});
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### 4. Query
|
|
63
|
+
|
|
64
|
+
Access the better-sqlite3 `Database` instance through the `db` getter:
|
|
65
|
+
|
|
66
|
+
```javascript
|
|
67
|
+
let tmpDB = _Fable.MeadowSQLiteProvider.db;
|
|
68
|
+
|
|
69
|
+
// Create a table
|
|
70
|
+
tmpDB.exec(`
|
|
71
|
+
CREATE TABLE IF NOT EXISTS Book (
|
|
72
|
+
IDBook INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
73
|
+
Title TEXT DEFAULT '',
|
|
74
|
+
Author TEXT DEFAULT ''
|
|
75
|
+
)
|
|
76
|
+
`);
|
|
77
|
+
|
|
78
|
+
// Insert a row
|
|
79
|
+
let tmpResult = tmpDB.prepare('INSERT INTO Book (Title, Author) VALUES (?, ?)').run('Dune', 'Frank Herbert');
|
|
80
|
+
console.log(`Inserted IDBook = ${tmpResult.lastInsertRowid}`);
|
|
81
|
+
|
|
82
|
+
// Query rows
|
|
83
|
+
let tmpBooks = tmpDB.prepare('SELECT * FROM Book').all();
|
|
84
|
+
console.log(tmpBooks);
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Configuration
|
|
88
|
+
|
|
89
|
+
The provider reads `SQLiteFilePath` from two sources, in order of priority:
|
|
90
|
+
|
|
91
|
+
1. **Constructor options** — passed as the second argument to `instantiateServiceProvider()`
|
|
92
|
+
2. **Fable settings** — `fable.settings.SQLite.SQLiteFilePath`
|
|
93
|
+
|
|
94
|
+
| Setting | Type | Description |
|
|
95
|
+
|---------|------|-------------|
|
|
96
|
+
| `SQLiteFilePath` | string | Path to the database file. Use `:memory:` for an ephemeral in-memory database. |
|
|
97
|
+
|
|
98
|
+
Any additional properties in the options object are passed through to the `better-sqlite3` constructor (e.g. `readonly`, `fileMustExist`, `timeout`).
|
|
99
|
+
|
|
100
|
+
### In-Memory Databases
|
|
101
|
+
|
|
102
|
+
Pass `:memory:` as the file path for a database that exists only in RAM. This is useful for fast test suites:
|
|
103
|
+
|
|
104
|
+
```javascript
|
|
105
|
+
let _Fable = new libFable(
|
|
106
|
+
{
|
|
107
|
+
"Product": "TestSuite",
|
|
108
|
+
"SQLite": { "SQLiteFilePath": ":memory:" }
|
|
109
|
+
});
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## How It Works
|
|
113
|
+
|
|
114
|
+
```
|
|
115
|
+
┌─────────────────────────────┐
|
|
116
|
+
│ Fable Application │
|
|
117
|
+
│ │
|
|
118
|
+
│ fable.settings.SQLite │
|
|
119
|
+
│ └── SQLiteFilePath │
|
|
120
|
+
└───────────┬─────────────────┘
|
|
121
|
+
│ connectAsync()
|
|
122
|
+
▼
|
|
123
|
+
┌─────────────────────────────┐
|
|
124
|
+
│ MeadowConnectionSQLite │
|
|
125
|
+
│ (Fable Service Provider) │
|
|
126
|
+
│ │
|
|
127
|
+
│ .connected │
|
|
128
|
+
│ .db ──────────────────┐ │
|
|
129
|
+
│ .SQLite │ │
|
|
130
|
+
└────────────────────────┼────┘
|
|
131
|
+
│
|
|
132
|
+
┌────────────▼────────────┐
|
|
133
|
+
│ better-sqlite3 │
|
|
134
|
+
│ │
|
|
135
|
+
│ .prepare(sql).run() │
|
|
136
|
+
│ .prepare(sql).get() │
|
|
137
|
+
│ .prepare(sql).all() │
|
|
138
|
+
│ .exec(sql) │
|
|
139
|
+
│ .transaction(fn) │
|
|
140
|
+
│ .pragma(str) │
|
|
141
|
+
└─────────────────────────┘
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
The provider manages the connection lifecycle and exposes the raw `better-sqlite3` `Database` object. All queries go through better-sqlite3's synchronous API — there are no promises or callbacks for individual statements.
|
|
145
|
+
|
|
146
|
+
## Companion Modules
|
|
147
|
+
|
|
148
|
+
| Module | Purpose |
|
|
149
|
+
|--------|---------|
|
|
150
|
+
| [Meadow](/meadow/meadow/) | ORM and data access layer |
|
|
151
|
+
| [FoxHound](/meadow/foxhound/) | Query DSL and SQL generation |
|
|
152
|
+
| [meadow-connection-mysql](/meadow/meadow-connection-mysql/) | MySQL connection provider |
|
|
153
|
+
| [meadow-connection-mssql](/meadow/meadow-connection-mssql/) | MSSQL connection provider |
|
package/docs/_sidebar.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
- Getting Started
|
|
2
|
+
|
|
3
|
+
- [Overview](README.md)
|
|
4
|
+
|
|
5
|
+
- Reference
|
|
6
|
+
|
|
7
|
+
- [API Reference](api.md)
|
|
8
|
+
|
|
9
|
+
- Examples
|
|
10
|
+
|
|
11
|
+
- [Full Pipeline](examples-pipeline.md)
|
|
12
|
+
|
|
13
|
+
- Retold Ecosystem
|
|
14
|
+
|
|
15
|
+
- [Meadow](/meadow/meadow/)
|
|
16
|
+
- [FoxHound](/meadow/foxhound/)
|
|
17
|
+
- [MySQL Connector](/meadow/meadow-connection-mysql/)
|
|
18
|
+
- [MSSQL Connector](/meadow/meadow-connection-mssql/)
|
|
19
|
+
- [Fable](/fable/fable/)
|
|
20
|
+
- [Indoctrinate](/utility/indoctrinate/)
|