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/docs/api.md
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
# API Reference
|
|
2
|
+
|
|
3
|
+
## Class: MeadowConnectionSQLite
|
|
4
|
+
|
|
5
|
+
Extends `fable-serviceproviderbase`. Manages a connection to a SQLite database file through the better-sqlite3 library.
|
|
6
|
+
|
|
7
|
+
### Constructor
|
|
8
|
+
|
|
9
|
+
```javascript
|
|
10
|
+
new MeadowConnectionSQLite(pFable, pManifest, pServiceHash)
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
| Parameter | Type | Description |
|
|
14
|
+
|-----------|------|-------------|
|
|
15
|
+
| `pFable` | object | A Fable instance |
|
|
16
|
+
| `pManifest` | object | Service manifest / options (optional) |
|
|
17
|
+
| `pServiceHash` | string | Service identifier |
|
|
18
|
+
|
|
19
|
+
On construction:
|
|
20
|
+
|
|
21
|
+
- Sets `serviceType` to `'MeadowConnectionSQLite'`
|
|
22
|
+
- Sets `connected` to `false`
|
|
23
|
+
- Reads `SQLiteFilePath` from `fable.settings.SQLite` if available
|
|
24
|
+
|
|
25
|
+
The provider is not yet connected after construction — call `connectAsync()` to open the database.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Properties
|
|
30
|
+
|
|
31
|
+
### connected
|
|
32
|
+
|
|
33
|
+
Whether the database connection is open.
|
|
34
|
+
|
|
35
|
+
**Type:** `boolean`
|
|
36
|
+
|
|
37
|
+
### db
|
|
38
|
+
|
|
39
|
+
The raw `better-sqlite3` `Database` instance. Use this for all query operations. Returns `false` before `connectAsync()` is called.
|
|
40
|
+
|
|
41
|
+
**Type:** `Database | false`
|
|
42
|
+
|
|
43
|
+
```javascript
|
|
44
|
+
let tmpDB = _Fable.MeadowSQLiteProvider.db;
|
|
45
|
+
|
|
46
|
+
// Synchronous query methods from better-sqlite3:
|
|
47
|
+
tmpDB.prepare(sql).run(params); // INSERT, UPDATE, DELETE
|
|
48
|
+
tmpDB.prepare(sql).get(params); // SELECT single row
|
|
49
|
+
tmpDB.prepare(sql).all(params); // SELECT all rows
|
|
50
|
+
tmpDB.exec(sql); // Execute raw SQL (multi-statement)
|
|
51
|
+
tmpDB.transaction(fn); // Wrap a function in a transaction
|
|
52
|
+
tmpDB.pragma(string); // Run a PRAGMA command
|
|
53
|
+
tmpDB.close(); // Close the database
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### SQLite
|
|
57
|
+
|
|
58
|
+
Reference to the `better-sqlite3` module constructor. Useful for accessing type constants or creating additional database instances.
|
|
59
|
+
|
|
60
|
+
**Type:** `function`
|
|
61
|
+
|
|
62
|
+
```javascript
|
|
63
|
+
let tmpSQLiteConstructor = _Fable.MeadowSQLiteProvider.SQLite;
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### serviceType
|
|
67
|
+
|
|
68
|
+
Always `'MeadowConnectionSQLite'`.
|
|
69
|
+
|
|
70
|
+
**Type:** `string`
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Methods
|
|
75
|
+
|
|
76
|
+
### connectAsync(fCallback)
|
|
77
|
+
|
|
78
|
+
Open a connection to the SQLite database file specified in configuration.
|
|
79
|
+
|
|
80
|
+
| Parameter | Type | Description |
|
|
81
|
+
|-----------|------|-------------|
|
|
82
|
+
| `fCallback` | function | Callback: `(error, database)` |
|
|
83
|
+
|
|
84
|
+
**Behavior:**
|
|
85
|
+
|
|
86
|
+
- If `SQLiteFilePath` is not configured, calls back with an error
|
|
87
|
+
- If already connected, calls back immediately with the existing database (idempotent)
|
|
88
|
+
- Creates the database file if it does not exist
|
|
89
|
+
- Enables WAL (Write-Ahead Logging) journal mode for performance
|
|
90
|
+
- Sets `this.connected = true` on success
|
|
91
|
+
|
|
92
|
+
```javascript
|
|
93
|
+
_Fable.MeadowSQLiteProvider.connectAsync(
|
|
94
|
+
(pError, pDatabase) =>
|
|
95
|
+
{
|
|
96
|
+
if (pError)
|
|
97
|
+
{
|
|
98
|
+
_Fable.log.error(`Connection failed: ${pError}`);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// pDatabase is the same as _Fable.MeadowSQLiteProvider.db
|
|
103
|
+
pDatabase.exec('CREATE TABLE IF NOT EXISTS Test (id INTEGER PRIMARY KEY)');
|
|
104
|
+
});
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### connect()
|
|
108
|
+
|
|
109
|
+
Synchronous wrapper that calls `connectAsync()` without a callback. Logs a warning about potential race conditions. Prefer `connectAsync()`.
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Configuration
|
|
114
|
+
|
|
115
|
+
### Fable Settings
|
|
116
|
+
|
|
117
|
+
```json
|
|
118
|
+
{
|
|
119
|
+
"SQLite":
|
|
120
|
+
{
|
|
121
|
+
"SQLiteFilePath": "./data/myapp.db"
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
| Setting | Type | Required | Description |
|
|
127
|
+
|---------|------|----------|-------------|
|
|
128
|
+
| `SQLiteFilePath` | string | Yes | File path or `:memory:` for in-memory databases |
|
|
129
|
+
|
|
130
|
+
Additional properties are passed through to the `better-sqlite3` constructor:
|
|
131
|
+
|
|
132
|
+
| Option | Type | Default | Description |
|
|
133
|
+
|--------|------|---------|-------------|
|
|
134
|
+
| `readonly` | boolean | `false` | Open the database in read-only mode |
|
|
135
|
+
| `fileMustExist` | boolean | `false` | Error if the file does not exist |
|
|
136
|
+
| `timeout` | number | `5000` | Milliseconds to wait when the database is locked |
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## Service Registration
|
|
141
|
+
|
|
142
|
+
The provider integrates with Fable's service manager:
|
|
143
|
+
|
|
144
|
+
```javascript
|
|
145
|
+
const libMeadowConnectionSQLite = require('meadow-connection-sqlite');
|
|
146
|
+
|
|
147
|
+
// Register the service type
|
|
148
|
+
_Fable.serviceManager.addServiceType('MeadowSQLiteProvider', libMeadowConnectionSQLite);
|
|
149
|
+
|
|
150
|
+
// Instantiate (optionally with per-instance options)
|
|
151
|
+
_Fable.serviceManager.instantiateServiceProvider('MeadowSQLiteProvider',
|
|
152
|
+
{
|
|
153
|
+
SQLiteFilePath: './data/alternate.db',
|
|
154
|
+
readonly: true
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// Access the provider
|
|
158
|
+
_Fable.MeadowSQLiteProvider.connectAsync((pError) => { /* ... */ });
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Constructor options override Fable settings, allowing multiple providers with different configurations.
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## better-sqlite3 Query API
|
|
166
|
+
|
|
167
|
+
After connecting, all queries go through the `db` getter. Here is a quick reference for the better-sqlite3 methods you will use most:
|
|
168
|
+
|
|
169
|
+
### Execute Raw SQL
|
|
170
|
+
|
|
171
|
+
```javascript
|
|
172
|
+
tmpDB.exec(`
|
|
173
|
+
CREATE TABLE IF NOT EXISTS Book (
|
|
174
|
+
IDBook INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
175
|
+
Title TEXT DEFAULT ''
|
|
176
|
+
);
|
|
177
|
+
CREATE INDEX IF NOT EXISTS idx_book_title ON Book(Title);
|
|
178
|
+
`);
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
`exec()` runs one or more SQL statements. It does not return data.
|
|
182
|
+
|
|
183
|
+
### Prepared Statements
|
|
184
|
+
|
|
185
|
+
```javascript
|
|
186
|
+
let tmpStmt = tmpDB.prepare('SELECT * FROM Book WHERE Author = ?');
|
|
187
|
+
|
|
188
|
+
// Single row
|
|
189
|
+
let tmpRow = tmpStmt.get('Frank Herbert');
|
|
190
|
+
|
|
191
|
+
// All rows
|
|
192
|
+
let tmpRows = tmpStmt.all('Frank Herbert');
|
|
193
|
+
|
|
194
|
+
// Modification
|
|
195
|
+
let tmpInsert = tmpDB.prepare('INSERT INTO Book (Title, Author) VALUES (?, ?)');
|
|
196
|
+
let tmpInfo = tmpInsert.run('Dune', 'Frank Herbert');
|
|
197
|
+
// tmpInfo.changes = 1, tmpInfo.lastInsertRowid = 1
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### Named Parameters
|
|
201
|
+
|
|
202
|
+
```javascript
|
|
203
|
+
let tmpStmt = tmpDB.prepare('SELECT * FROM Book WHERE Author = @author AND YearPublished > @year');
|
|
204
|
+
let tmpRows = tmpStmt.all({ author: 'Isaac Asimov', year: 1950 });
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### Transactions
|
|
208
|
+
|
|
209
|
+
```javascript
|
|
210
|
+
let tmpBulkInsert = tmpDB.transaction(
|
|
211
|
+
(pBooks) =>
|
|
212
|
+
{
|
|
213
|
+
let tmpStmt = tmpDB.prepare('INSERT INTO Book (Title, Author) VALUES (?, ?)');
|
|
214
|
+
for (let tmpBook of pBooks)
|
|
215
|
+
{
|
|
216
|
+
tmpStmt.run(tmpBook.Title, tmpBook.Author);
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
// The entire array is inserted atomically
|
|
221
|
+
tmpBulkInsert([
|
|
222
|
+
{ Title: 'Foundation', Author: 'Isaac Asimov' },
|
|
223
|
+
{ Title: 'Snow Crash', Author: 'Neal Stephenson' }
|
|
224
|
+
]);
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
If any statement inside the transaction throws, the entire transaction is rolled back automatically.
|
|
228
|
+
|
|
229
|
+
### Close
|
|
230
|
+
|
|
231
|
+
```javascript
|
|
232
|
+
tmpDB.close();
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
## Logging
|
|
238
|
+
|
|
239
|
+
The provider logs connection events through the Fable logging system:
|
|
240
|
+
|
|
241
|
+
| Event | Level | Message |
|
|
242
|
+
|-------|-------|---------|
|
|
243
|
+
| Connecting | `info` | `Meadow-Connection-SQLite connecting to file [path].` |
|
|
244
|
+
| Connected | `info` | `Meadow-Connection-SQLite successfully connected to SQLite file [path].` |
|
|
245
|
+
| Already connected | `error` | `...is already connected - skipping the second connect call.` |
|
|
246
|
+
| Missing path | `error` | `...database file path is invalid; SQLiteFilePath must be in either...` |
|
|
247
|
+
| Connection error | `error` | `...error connecting to SQLite file [path]: [error]` |
|
|
248
|
+
| No callback | `error` | `...connect() called without a callback...` |
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
## Known Limitations
|
|
253
|
+
|
|
254
|
+
- **Table generation** — The `generateCreateTableStatement()` and `createTable()` methods produce MSSQL-syntax SQL that is not compatible with SQLite. Use `db.exec()` with SQLite-native `CREATE TABLE` statements instead.
|
|
255
|
+
- **Prepared statement getter** — The `preparedStatement` property references an uninitialized connection pool. Use `db.prepare()` directly for prepared statements.
|
|
256
|
+
- **No async queries** — better-sqlite3 is synchronous by design. For CPU-bound workloads, consider running queries in a worker thread.
|
package/docs/cover.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Meadow Connection SQLite
|
|
2
|
+
|
|
3
|
+
> SQLite database provider for the Meadow data layer
|
|
4
|
+
|
|
5
|
+
Connect any Fable application to a local SQLite database through the service provider pattern. Built on better-sqlite3 for fast synchronous access with WAL journaling, automatic file creation, and in-memory database support.
|
|
6
|
+
|
|
7
|
+
- **Zero Config Server** -- No daemon, no Docker — just a file path and you have a database
|
|
8
|
+
- **Synchronous API** -- All queries run synchronously through better-sqlite3's native bindings
|
|
9
|
+
- **WAL Journaling** -- Write-Ahead Logging enabled automatically for concurrent read/write performance
|
|
10
|
+
- **In-Memory Mode** -- Use `:memory:` as the file path for ephemeral databases in tests and prototypes
|
|
11
|
+
- **Service Integration** -- Registers as a Fable service with dependency injection and lifecycle logging
|
|
12
|
+
|
|
13
|
+
[Quick Start](README.md)
|
|
14
|
+
[API Reference](api.md)
|
|
15
|
+
[GitHub](https://github.com/stevenvelozo/meadow-connection-sqlite)
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/* ============================================================================
|
|
2
|
+
Pict Docuserve - Base Styles
|
|
3
|
+
============================================================================ */
|
|
4
|
+
|
|
5
|
+
/* Reset and base */
|
|
6
|
+
*, *::before, *::after {
|
|
7
|
+
box-sizing: border-box;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
html, body {
|
|
11
|
+
margin: 0;
|
|
12
|
+
padding: 0;
|
|
13
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
14
|
+
font-size: 16px;
|
|
15
|
+
line-height: 1.5;
|
|
16
|
+
color: #423D37;
|
|
17
|
+
background-color: #fff;
|
|
18
|
+
-webkit-font-smoothing: antialiased;
|
|
19
|
+
-moz-osx-font-smoothing: grayscale;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/* Typography */
|
|
23
|
+
h1, h2, h3, h4, h5, h6 {
|
|
24
|
+
margin-top: 0;
|
|
25
|
+
line-height: 1.3;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
a {
|
|
29
|
+
color: #2E7D74;
|
|
30
|
+
text-decoration: none;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
a:hover {
|
|
34
|
+
color: #256861;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/* Application container */
|
|
38
|
+
#Docuserve-Application-Container {
|
|
39
|
+
min-height: 100vh;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/* Utility: scrollbar styling */
|
|
43
|
+
::-webkit-scrollbar {
|
|
44
|
+
width: 8px;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
::-webkit-scrollbar-track {
|
|
48
|
+
background: #F5F0E8;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
::-webkit-scrollbar-thumb {
|
|
52
|
+
background: #D4CCBE;
|
|
53
|
+
border-radius: 4px;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
::-webkit-scrollbar-thumb:hover {
|
|
57
|
+
background: #B5AA9A;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/* Responsive adjustments */
|
|
61
|
+
@media (max-width: 768px) {
|
|
62
|
+
html {
|
|
63
|
+
font-size: 14px;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
#Docuserve-Sidebar-Container {
|
|
67
|
+
display: none;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.docuserve-body {
|
|
71
|
+
flex-direction: column;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
# Full Pipeline Example
|
|
2
|
+
|
|
3
|
+
This walkthrough builds a complete CRUD pipeline from scratch: configure Fable, connect to SQLite, create a schema, insert rows, query, update, delete, run a transaction, and compute aggregates. Every snippet below has been executed and verified against meadow-connection-sqlite.
|
|
4
|
+
|
|
5
|
+
> The full script is available at `debug/Pipeline-Test.js` in the module repository.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Setup
|
|
10
|
+
|
|
11
|
+
```javascript
|
|
12
|
+
const libFable = require('fable');
|
|
13
|
+
const libMeadowConnectionSQLite = require('meadow-connection-sqlite');
|
|
14
|
+
|
|
15
|
+
let _Fable = new libFable(
|
|
16
|
+
{
|
|
17
|
+
"Product": "BookstoreExample",
|
|
18
|
+
"ProductVersion": "1.0.0",
|
|
19
|
+
"UUID": { "DataCenter": 0, "Worker": 0 },
|
|
20
|
+
"LogStreams": [{ "streamtype": "console" }],
|
|
21
|
+
"SQLite": { "SQLiteFilePath": "./dist/Bookstore.db" }
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
_Fable.serviceManager.addServiceType('MeadowSQLiteProvider', libMeadowConnectionSQLite);
|
|
25
|
+
_Fable.serviceManager.instantiateServiceProvider('MeadowSQLiteProvider');
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Step 1: Connect
|
|
31
|
+
|
|
32
|
+
```javascript
|
|
33
|
+
_Fable.MeadowSQLiteProvider.connectAsync(
|
|
34
|
+
(pError, pDatabase) =>
|
|
35
|
+
{
|
|
36
|
+
if (pError)
|
|
37
|
+
{
|
|
38
|
+
_Fable.log.error(`Connection failed: ${pError}`);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
_Fable.log.info(`Connected: ${_Fable.MeadowSQLiteProvider.connected}`);
|
|
43
|
+
// => Connected: true
|
|
44
|
+
|
|
45
|
+
let tmpDB = _Fable.MeadowSQLiteProvider.db;
|
|
46
|
+
|
|
47
|
+
// ... all remaining steps use tmpDB ...
|
|
48
|
+
});
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
The database file is created automatically if it does not exist. WAL journal mode is enabled on connection for better performance.
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Step 2: Create a Table
|
|
56
|
+
|
|
57
|
+
Use SQLite-native `CREATE TABLE` syntax through `db.exec()`:
|
|
58
|
+
|
|
59
|
+
```javascript
|
|
60
|
+
tmpDB.exec(`
|
|
61
|
+
CREATE TABLE IF NOT EXISTS Book (
|
|
62
|
+
IDBook INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
63
|
+
GUIDBook TEXT DEFAULT '00000000-0000-0000-0000-000000000000',
|
|
64
|
+
Title TEXT DEFAULT '',
|
|
65
|
+
Author TEXT DEFAULT '',
|
|
66
|
+
YearPublished INTEGER DEFAULT 0,
|
|
67
|
+
Price REAL DEFAULT 0.0,
|
|
68
|
+
CreateDate TEXT DEFAULT (datetime('now')),
|
|
69
|
+
UpdateDate TEXT DEFAULT (datetime('now'))
|
|
70
|
+
)
|
|
71
|
+
`);
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**SQLite type mapping:**
|
|
75
|
+
|
|
76
|
+
| Meadow Type | SQLite Type | Notes |
|
|
77
|
+
|-------------|-------------|-------|
|
|
78
|
+
| ID | `INTEGER PRIMARY KEY AUTOINCREMENT` | Auto-incrementing primary key |
|
|
79
|
+
| GUID | `TEXT` | Store as 36-character UUID string |
|
|
80
|
+
| String | `TEXT` | SQLite does not enforce VARCHAR length |
|
|
81
|
+
| Numeric / ForeignKey | `INTEGER` | 64-bit signed integer |
|
|
82
|
+
| Decimal | `REAL` | 64-bit IEEE floating point |
|
|
83
|
+
| Boolean | `INTEGER` | 0 = false, 1 = true |
|
|
84
|
+
| DateTime | `TEXT` | ISO-8601 string, e.g. `datetime('now')` |
|
|
85
|
+
| Text | `TEXT` | Unlimited length |
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## Step 3: Insert Rows
|
|
90
|
+
|
|
91
|
+
Prepared statements with positional parameters:
|
|
92
|
+
|
|
93
|
+
```javascript
|
|
94
|
+
let tmpInsert = tmpDB.prepare(
|
|
95
|
+
`INSERT INTO Book (GUIDBook, Title, Author, YearPublished, Price) VALUES (?, ?, ?, ?, ?)`
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
let tmpResult1 = tmpInsert.run(_Fable.fable.getUUID(), 'The Hobbit', 'J.R.R. Tolkien', 1937, 12.99);
|
|
99
|
+
// tmpResult1.lastInsertRowid => 1
|
|
100
|
+
|
|
101
|
+
let tmpResult2 = tmpInsert.run(_Fable.fable.getUUID(), 'Dune', 'Frank Herbert', 1965, 14.99);
|
|
102
|
+
// tmpResult2.lastInsertRowid => 2
|
|
103
|
+
|
|
104
|
+
let tmpResult3 = tmpInsert.run(_Fable.fable.getUUID(), 'Neuromancer', 'William Gibson', 1984, 11.50);
|
|
105
|
+
// tmpResult3.lastInsertRowid => 3
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
The `run()` method returns an object with two properties:
|
|
109
|
+
|
|
110
|
+
| Property | Type | Description |
|
|
111
|
+
|----------|------|-------------|
|
|
112
|
+
| `changes` | number | Number of rows affected |
|
|
113
|
+
| `lastInsertRowid` | number | Auto-generated ID of the inserted row |
|
|
114
|
+
|
|
115
|
+
Fable's `getUUID()` generates unique identifiers for the GUID column.
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## Step 4: Read All Rows
|
|
120
|
+
|
|
121
|
+
```javascript
|
|
122
|
+
let tmpAllBooks = tmpDB.prepare(`SELECT * FROM Book`).all();
|
|
123
|
+
// tmpAllBooks.length => 3
|
|
124
|
+
|
|
125
|
+
for (let i = 0; i < tmpAllBooks.length; i++)
|
|
126
|
+
{
|
|
127
|
+
let tmpBook = tmpAllBooks[i];
|
|
128
|
+
console.log(`[${tmpBook.IDBook}] "${tmpBook.Title}" by ${tmpBook.Author} ($${tmpBook.Price})`);
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Output:
|
|
133
|
+
|
|
134
|
+
```
|
|
135
|
+
[1] "The Hobbit" by J.R.R. Tolkien ($12.99)
|
|
136
|
+
[2] "Dune" by Frank Herbert ($14.99)
|
|
137
|
+
[3] "Neuromancer" by William Gibson ($11.5)
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
The `all()` method returns an array of plain JavaScript objects. Column names become property keys.
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## Step 5: Read a Single Row
|
|
145
|
+
|
|
146
|
+
```javascript
|
|
147
|
+
let tmpOneBook = tmpDB.prepare(`SELECT * FROM Book WHERE IDBook = ?`).get(2);
|
|
148
|
+
// tmpOneBook => { IDBook: 2, Title: 'Dune', Author: 'Frank Herbert', ... }
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
The `get()` method returns a single object, or `undefined` if no row matches.
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## Step 6: Update
|
|
156
|
+
|
|
157
|
+
```javascript
|
|
158
|
+
let tmpUpdate = tmpDB.prepare(
|
|
159
|
+
`UPDATE Book SET Price = ?, UpdateDate = datetime('now') WHERE IDBook = ?`
|
|
160
|
+
);
|
|
161
|
+
let tmpUpdateResult = tmpUpdate.run(16.99, 2);
|
|
162
|
+
// tmpUpdateResult.changes => 1
|
|
163
|
+
|
|
164
|
+
let tmpVerify = tmpDB.prepare(`SELECT * FROM Book WHERE IDBook = ?`).get(2);
|
|
165
|
+
// tmpVerify.Price => 16.99
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## Step 7: Delete
|
|
171
|
+
|
|
172
|
+
```javascript
|
|
173
|
+
let tmpDelete = tmpDB.prepare(`DELETE FROM Book WHERE IDBook = ?`);
|
|
174
|
+
let tmpDeleteResult = tmpDelete.run(3);
|
|
175
|
+
// tmpDeleteResult.changes => 1
|
|
176
|
+
|
|
177
|
+
let tmpRemaining = tmpDB.prepare(`SELECT * FROM Book`).all();
|
|
178
|
+
// tmpRemaining.length => 2
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## Step 8: Transactions
|
|
184
|
+
|
|
185
|
+
Transactions in better-sqlite3 wrap a function. If anything inside the function throws, the entire transaction is rolled back. Otherwise it commits atomically.
|
|
186
|
+
|
|
187
|
+
```javascript
|
|
188
|
+
let tmpBulkInsert = tmpDB.transaction(
|
|
189
|
+
(pBooks) =>
|
|
190
|
+
{
|
|
191
|
+
let tmpStmt = tmpDB.prepare(
|
|
192
|
+
`INSERT INTO Book (GUIDBook, Title, Author, YearPublished, Price) VALUES (?, ?, ?, ?, ?)`
|
|
193
|
+
);
|
|
194
|
+
for (let tmpBook of pBooks)
|
|
195
|
+
{
|
|
196
|
+
tmpStmt.run(tmpBook.GUID, tmpBook.Title, tmpBook.Author, tmpBook.Year, tmpBook.Price);
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
tmpBulkInsert(
|
|
201
|
+
[
|
|
202
|
+
{ GUID: _Fable.fable.getUUID(), Title: 'Snow Crash', Author: 'Neal Stephenson', Year: 1992, Price: 13.99 },
|
|
203
|
+
{ GUID: _Fable.fable.getUUID(), Title: 'Hyperion', Author: 'Dan Simmons', Year: 1989, Price: 15.50 },
|
|
204
|
+
{ GUID: _Fable.fable.getUUID(), Title: 'Foundation', Author: 'Isaac Asimov', Year: 1951, Price: 10.99 }
|
|
205
|
+
]);
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
After the transaction, querying shows all five books:
|
|
209
|
+
|
|
210
|
+
```
|
|
211
|
+
[1] "The Hobbit" by J.R.R. Tolkien ($12.99)
|
|
212
|
+
[2] "Dune" by Frank Herbert ($16.99)
|
|
213
|
+
[4] "Snow Crash" by Neal Stephenson ($13.99)
|
|
214
|
+
[5] "Hyperion" by Dan Simmons ($15.5)
|
|
215
|
+
[6] "Foundation" by Isaac Asimov ($10.99)
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Note that IDBook 3 is absent (deleted in Step 7) and the sequence skips to 4.
|
|
219
|
+
|
|
220
|
+
Transactions are significantly faster than individual inserts for bulk operations. Inserting 1,000 rows in a transaction can be 50-100x faster than 1,000 separate `run()` calls.
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
## Step 9: Aggregates
|
|
225
|
+
|
|
226
|
+
```javascript
|
|
227
|
+
let tmpStats = tmpDB.prepare(
|
|
228
|
+
`SELECT COUNT(*) as BookCount, AVG(Price) as AvgPrice, SUM(Price) as TotalValue FROM Book`
|
|
229
|
+
).get();
|
|
230
|
+
|
|
231
|
+
console.log(`Books: ${tmpStats.BookCount}`);
|
|
232
|
+
// => Books: 5
|
|
233
|
+
|
|
234
|
+
console.log(`Average price: $${tmpStats.AvgPrice.toFixed(2)}`);
|
|
235
|
+
// => Average price: $14.09
|
|
236
|
+
|
|
237
|
+
console.log(`Total value: $${tmpStats.TotalValue.toFixed(2)}`);
|
|
238
|
+
// => Total value: $70.46
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
## Step 10: Close
|
|
244
|
+
|
|
245
|
+
```javascript
|
|
246
|
+
tmpDB.close();
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
After closing, further queries on this database instance will throw. The provider's `connected` property remains `true` — if you need to reconnect, create a new provider instance.
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
## Named Parameters
|
|
254
|
+
|
|
255
|
+
better-sqlite3 supports named parameters prefixed with `@`, `$`, or `:`:
|
|
256
|
+
|
|
257
|
+
```javascript
|
|
258
|
+
let tmpStmt = tmpDB.prepare(
|
|
259
|
+
`SELECT * FROM Book WHERE Author = @author AND YearPublished > @minYear`
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
let tmpBooks = tmpStmt.all({ author: 'Isaac Asimov', minYear: 1950 });
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
Named parameters make complex queries more readable and less error-prone than positional `?` placeholders.
|
|
266
|
+
|
|
267
|
+
---
|
|
268
|
+
|
|
269
|
+
## In-Memory Databases
|
|
270
|
+
|
|
271
|
+
For test suites and ephemeral workflows, use `:memory:` instead of a file path:
|
|
272
|
+
|
|
273
|
+
```javascript
|
|
274
|
+
let _Fable = new libFable(
|
|
275
|
+
{
|
|
276
|
+
"Product": "TestSuite",
|
|
277
|
+
"SQLite": { "SQLiteFilePath": ":memory:" }
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
// Register, connect, create tables, run tests...
|
|
281
|
+
// Database is discarded when the process exits or the connection closes.
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
In-memory databases are faster than file-backed databases and leave no cleanup artifacts.
|
|
285
|
+
|
|
286
|
+
---
|
|
287
|
+
|
|
288
|
+
## Error Handling Pattern
|
|
289
|
+
|
|
290
|
+
```javascript
|
|
291
|
+
_Fable.MeadowSQLiteProvider.connectAsync(
|
|
292
|
+
(pError) =>
|
|
293
|
+
{
|
|
294
|
+
if (pError)
|
|
295
|
+
{
|
|
296
|
+
_Fable.log.error(`Connection error: ${pError.message}`);
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
let tmpDB = _Fable.MeadowSQLiteProvider.db;
|
|
301
|
+
|
|
302
|
+
try
|
|
303
|
+
{
|
|
304
|
+
tmpDB.exec(`CREATE TABLE Test (id INTEGER PRIMARY KEY)`);
|
|
305
|
+
tmpDB.prepare(`INSERT INTO Test (id) VALUES (?)`).run(1);
|
|
306
|
+
}
|
|
307
|
+
catch (pQueryError)
|
|
308
|
+
{
|
|
309
|
+
_Fable.log.error(`Query error: ${pQueryError.message}`);
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
better-sqlite3 throws `SqliteError` exceptions for all query failures (syntax errors, constraint violations, missing tables). Wrap query blocks in try/catch for graceful handling.
|
|
315
|
+
|
|
316
|
+
---
|
|
317
|
+
|
|
318
|
+
## Pipeline Summary
|
|
319
|
+
|
|
320
|
+
| Step | Method | What It Does |
|
|
321
|
+
|------|--------|-------------|
|
|
322
|
+
| Connect | `connectAsync(cb)` | Opens the database file, enables WAL |
|
|
323
|
+
| Create | `db.exec(sql)` | Runs DDL statements |
|
|
324
|
+
| Insert | `db.prepare(sql).run(...)` | Returns `{ changes, lastInsertRowid }` |
|
|
325
|
+
| Read all | `db.prepare(sql).all(...)` | Returns array of row objects |
|
|
326
|
+
| Read one | `db.prepare(sql).get(...)` | Returns single object or `undefined` |
|
|
327
|
+
| Update | `db.prepare(sql).run(...)` | Returns `{ changes }` |
|
|
328
|
+
| Delete | `db.prepare(sql).run(...)` | Returns `{ changes }` |
|
|
329
|
+
| Transaction | `db.transaction(fn)(args)` | Atomic batch; auto-rollback on throw |
|
|
330
|
+
| Aggregate | `db.prepare(sql).get()` | Returns computed values |
|
|
331
|
+
| Close | `db.close()` | Releases file handle |
|
package/docs/index.html
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
|
7
|
+
<meta name="description" content="Documentation powered by pict-docuserve">
|
|
8
|
+
|
|
9
|
+
<title>Documentation</title>
|
|
10
|
+
|
|
11
|
+
<!-- Application Stylesheet -->
|
|
12
|
+
<link href="css/docuserve.css" rel="stylesheet">
|
|
13
|
+
<!-- KaTeX stylesheet for LaTeX equation rendering -->
|
|
14
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.21/dist/katex.min.css">
|
|
15
|
+
<!-- PICT Dynamic View CSS Container -->
|
|
16
|
+
<style id="PICT-CSS"></style>
|
|
17
|
+
|
|
18
|
+
<!-- Load the PICT library from jsDelivr CDN -->
|
|
19
|
+
<script src="https://cdn.jsdelivr.net/npm/pict@1/dist/pict.min.js" type="text/javascript"></script>
|
|
20
|
+
<!-- Bootstrap the Application -->
|
|
21
|
+
<script type="text/javascript">
|
|
22
|
+
//<![CDATA[
|
|
23
|
+
Pict.safeOnDocumentReady(() => { Pict.safeLoadPictApplication(PictDocuserve, 2)});
|
|
24
|
+
//]]>
|
|
25
|
+
</script>
|
|
26
|
+
</head>
|
|
27
|
+
<body>
|
|
28
|
+
<!-- The root container for the Pict application -->
|
|
29
|
+
<div id="Docuserve-Application-Container"></div>
|
|
30
|
+
|
|
31
|
+
<!-- Mermaid diagram rendering -->
|
|
32
|
+
<script src="https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js"></script>
|
|
33
|
+
<script>mermaid.initialize({ startOnLoad: false, theme: 'default' });</script>
|
|
34
|
+
<!-- KaTeX for LaTeX equation rendering -->
|
|
35
|
+
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.21/dist/katex.min.js"></script>
|
|
36
|
+
<!-- Load the Docuserve PICT Application Bundle from jsDelivr CDN -->
|
|
37
|
+
<script src="https://cdn.jsdelivr.net/npm/pict-docuserve@0/dist/pict-docuserve.min.js" type="text/javascript"></script>
|
|
38
|
+
</body>
|
|
39
|
+
</html>
|