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.
- package/README.md +174 -112
- package/docs/README.md +276 -0
- package/docs/_sidebar.md +38 -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 +16 -3
- 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 +235 -155
- package/test/Meadow-Provider-SQLite-AnimalReadQuery.sql +5 -0
- package/test/Meadow-Provider-SQLite_tests.js +931 -0
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
# Schema
|
|
2
|
+
|
|
3
|
+
> Define your data model once — get validation, defaults, audit tracking, and authorization for free
|
|
4
|
+
|
|
5
|
+
Meadow's schema system is the foundation of every data entity. It defines column structure, validation rules, default object templates, and authorization policies in a single declarative configuration. The schema drives everything from query generation to automatic audit stamping.
|
|
6
|
+
|
|
7
|
+
## Overview
|
|
8
|
+
|
|
9
|
+
A Meadow schema consists of four complementary parts:
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
Schema Definition
|
|
13
|
+
├── Column Schema (array) → Defines columns, types, and sizes
|
|
14
|
+
├── JSON Schema (object) → Validates objects against JSON Schema v4
|
|
15
|
+
├── Default Object (object) → Template merged with new records on create
|
|
16
|
+
└── Authorizer (object) → Role-based access control per operation
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Each part is optional and can be set independently, but together they provide a complete data model definition.
|
|
20
|
+
|
|
21
|
+
## Column Schema
|
|
22
|
+
|
|
23
|
+
The column schema is an array of objects that defines the columns in your entity. Each column has a `Column` name, a `Type`, and an optional `Size`.
|
|
24
|
+
|
|
25
|
+
```javascript
|
|
26
|
+
const tmpSchema =
|
|
27
|
+
[
|
|
28
|
+
{ Column: 'IDBook', Type: 'AutoIdentity' },
|
|
29
|
+
{ Column: 'GUIDBook', Type: 'AutoGUID' },
|
|
30
|
+
{ Column: 'Title', Type: 'String', Size: '255' },
|
|
31
|
+
{ Column: 'Author', Type: 'String', Size: '128' },
|
|
32
|
+
{ Column: 'PageCount', Type: 'Numeric' },
|
|
33
|
+
{ Column: 'Price', Type: 'Decimal', Size: '12,2' },
|
|
34
|
+
{ Column: 'InPrint', Type: 'Boolean' },
|
|
35
|
+
{ Column: 'PublishDate', Type: 'DateTime' },
|
|
36
|
+
{ Column: 'Synopsis', Type: 'Text' },
|
|
37
|
+
{ Column: 'CreateDate', Type: 'CreateDate' },
|
|
38
|
+
{ Column: 'CreatingIDUser', Type: 'CreateIDUser' },
|
|
39
|
+
{ Column: 'UpdateDate', Type: 'UpdateDate' },
|
|
40
|
+
{ Column: 'UpdatingIDUser', Type: 'UpdateIDUser' },
|
|
41
|
+
{ Column: 'DeleteDate', Type: 'DeleteDate' },
|
|
42
|
+
{ Column: 'DeletingIDUser', Type: 'DeleteIDUser' },
|
|
43
|
+
{ Column: 'Deleted', Type: 'Deleted' }
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
meadow.setSchema(tmpSchema);
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Column Types
|
|
50
|
+
|
|
51
|
+
| Type | Description | SQL Mapping | Default Value |
|
|
52
|
+
|------|-------------|-------------|---------------|
|
|
53
|
+
| `AutoIdentity` | Auto-increment primary key | `INT UNSIGNED AUTO_INCREMENT` | N/A (generated) |
|
|
54
|
+
| `AutoGUID` | Automatically generated GUID | `CHAR(36)` | `'00000000-0000-0000-0000-000000000000'` |
|
|
55
|
+
| `String` | Variable-length string | `VARCHAR(Size)` | `''` |
|
|
56
|
+
| `Text` | Long text field | `TEXT` | `null` |
|
|
57
|
+
| `Numeric` | Integer field | `INT` | `0` |
|
|
58
|
+
| `Integer` | Integer field (alias) | `INT` | `0` |
|
|
59
|
+
| `Decimal` | Decimal number | `DECIMAL(precision,scale)` | `0` |
|
|
60
|
+
| `Boolean` | Boolean flag | `INT` / `BOOLEAN` | `0` |
|
|
61
|
+
| `DateTime` | Date/time field | `DATETIME` | `null` |
|
|
62
|
+
|
|
63
|
+
### Audit Column Types
|
|
64
|
+
|
|
65
|
+
These special types trigger automatic behavior during CRUD operations:
|
|
66
|
+
|
|
67
|
+
| Type | Description | Auto-Populated |
|
|
68
|
+
|------|-------------|----------------|
|
|
69
|
+
| `CreateDate` | Timestamp when record was created | Set to current UTC time on `doCreate` |
|
|
70
|
+
| `CreateIDUser` | User who created the record | Set from `query.IDUser` on `doCreate` |
|
|
71
|
+
| `UpdateDate` | Timestamp of last update | Set to current UTC time on `doUpdate` |
|
|
72
|
+
| `UpdateIDUser` | User who last updated | Set from `query.IDUser` on `doUpdate` |
|
|
73
|
+
| `DeleteDate` | Timestamp of soft deletion | Set to current UTC time on `doDelete` |
|
|
74
|
+
| `DeleteIDUser` | User who soft deleted | Set from `query.IDUser` on `doDelete` |
|
|
75
|
+
| `Deleted` | Soft delete flag | Set to `1` on `doDelete`, `0` on `doUndelete` |
|
|
76
|
+
|
|
77
|
+
### Column Properties
|
|
78
|
+
|
|
79
|
+
| Property | Required | Description |
|
|
80
|
+
|----------|----------|-------------|
|
|
81
|
+
| `Column` | Yes | The column name in the database |
|
|
82
|
+
| `Type` | Yes | One of the column types listed above |
|
|
83
|
+
| `Size` | No | For `String`: max length (e.g., `'255'`). For `Decimal`: precision and scale (e.g., `'12,2'`) |
|
|
84
|
+
|
|
85
|
+
## JSON Schema Validation
|
|
86
|
+
|
|
87
|
+
Meadow supports JSON Schema v4 for validating objects before they reach the database. Validation uses the [is-my-json-valid](https://github.com/mafintosh/is-my-json-valid) library with greedy mode enabled.
|
|
88
|
+
|
|
89
|
+
```javascript
|
|
90
|
+
meadow.setJsonSchema({
|
|
91
|
+
title: 'Book',
|
|
92
|
+
description: 'A book in the catalog',
|
|
93
|
+
type: 'object',
|
|
94
|
+
properties:
|
|
95
|
+
{
|
|
96
|
+
IDBook:
|
|
97
|
+
{
|
|
98
|
+
description: 'The unique identifier for a book',
|
|
99
|
+
type: 'integer'
|
|
100
|
+
},
|
|
101
|
+
Title:
|
|
102
|
+
{
|
|
103
|
+
description: 'The book title',
|
|
104
|
+
type: 'string'
|
|
105
|
+
},
|
|
106
|
+
Author:
|
|
107
|
+
{
|
|
108
|
+
description: 'The book author',
|
|
109
|
+
type: 'string'
|
|
110
|
+
},
|
|
111
|
+
PageCount:
|
|
112
|
+
{
|
|
113
|
+
description: 'Number of pages',
|
|
114
|
+
type: 'integer'
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
required: ['Title']
|
|
118
|
+
});
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Validating Objects
|
|
122
|
+
|
|
123
|
+
```javascript
|
|
124
|
+
// Valid object
|
|
125
|
+
const tmpResult = meadow.schemaFull.validateObject(
|
|
126
|
+
{ IDBook: 1, Title: 'Dune', Author: 'Frank Herbert', PageCount: 412 });
|
|
127
|
+
// { Valid: true, Errors: [] }
|
|
128
|
+
|
|
129
|
+
// Invalid object (missing required field)
|
|
130
|
+
const tmpResult = meadow.schemaFull.validateObject(
|
|
131
|
+
{ IDBook: 2, Author: 'Isaac Asimov' });
|
|
132
|
+
// { Valid: false, Errors: [...] }
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
The `validateObject` method returns an object with:
|
|
136
|
+
|
|
137
|
+
| Property | Type | Description |
|
|
138
|
+
|----------|------|-------------|
|
|
139
|
+
| `Valid` | boolean | Whether the object passed validation |
|
|
140
|
+
| `Errors` | array | Validation errors (empty when valid) |
|
|
141
|
+
|
|
142
|
+
## Default Object
|
|
143
|
+
|
|
144
|
+
The default object is a template that gets merged with submitted record data during `doCreate` operations. This ensures every column has a value even if the caller doesn't provide one.
|
|
145
|
+
|
|
146
|
+
```javascript
|
|
147
|
+
meadow.setDefault({
|
|
148
|
+
IDBook: null,
|
|
149
|
+
GUIDBook: '',
|
|
150
|
+
Title: 'Untitled',
|
|
151
|
+
Author: 'Unknown',
|
|
152
|
+
PageCount: 0,
|
|
153
|
+
Price: 0,
|
|
154
|
+
InPrint: false,
|
|
155
|
+
Synopsis: null,
|
|
156
|
+
CreateDate: false,
|
|
157
|
+
CreatingIDUser: 0,
|
|
158
|
+
UpdateDate: false,
|
|
159
|
+
UpdatingIDUser: 0,
|
|
160
|
+
Deleted: 0,
|
|
161
|
+
DeletingIDUser: 0,
|
|
162
|
+
DeleteDate: false
|
|
163
|
+
});
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### How Default Merging Works
|
|
167
|
+
|
|
168
|
+
During `doCreate`, Meadow merges the default object with the submitted record:
|
|
169
|
+
|
|
170
|
+
```javascript
|
|
171
|
+
// Internally, Meadow does:
|
|
172
|
+
// finalRecord = extend({}, defaultObject, submittedRecord)
|
|
173
|
+
|
|
174
|
+
// If you create with:
|
|
175
|
+
meadow.doCreate(
|
|
176
|
+
meadow.query.addRecord({ Title: 'Neuromancer', Author: 'William Gibson' }),
|
|
177
|
+
(pError, pCreateQuery, pReadQuery, pRecord) =>
|
|
178
|
+
{
|
|
179
|
+
// pRecord will contain:
|
|
180
|
+
// {
|
|
181
|
+
// IDBook: <auto-generated>,
|
|
182
|
+
// GUIDBook: <auto-generated>,
|
|
183
|
+
// Title: 'Neuromancer', ← from submitted record
|
|
184
|
+
// Author: 'William Gibson', ← from submitted record
|
|
185
|
+
// PageCount: 0, ← from default object
|
|
186
|
+
// Price: 0, ← from default object
|
|
187
|
+
// InPrint: false, ← from default object
|
|
188
|
+
// ...
|
|
189
|
+
// }
|
|
190
|
+
});
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## Authorizer
|
|
194
|
+
|
|
195
|
+
The authorizer defines role-based access control for each CRUD operation. Roles map to permission rules that Meadow-Endpoints uses to gate API access.
|
|
196
|
+
|
|
197
|
+
```javascript
|
|
198
|
+
meadow.setAuthorizer({
|
|
199
|
+
Unauthenticated:
|
|
200
|
+
{
|
|
201
|
+
Create: 'Deny',
|
|
202
|
+
Read: 'Deny',
|
|
203
|
+
Reads: 'Deny',
|
|
204
|
+
Update: 'Deny',
|
|
205
|
+
Delete: 'Deny',
|
|
206
|
+
Count: 'Deny',
|
|
207
|
+
Schema: 'Deny'
|
|
208
|
+
},
|
|
209
|
+
User:
|
|
210
|
+
{
|
|
211
|
+
Create: 'Allow',
|
|
212
|
+
Read: 'Allow',
|
|
213
|
+
Reads: 'Allow',
|
|
214
|
+
Update: 'Mine',
|
|
215
|
+
Delete: 'Mine',
|
|
216
|
+
Count: 'Allow',
|
|
217
|
+
Schema: 'Allow'
|
|
218
|
+
},
|
|
219
|
+
Administrator:
|
|
220
|
+
{
|
|
221
|
+
Create: 'Allow',
|
|
222
|
+
Read: 'Allow',
|
|
223
|
+
Reads: 'Allow',
|
|
224
|
+
Update: 'Allow',
|
|
225
|
+
Delete: 'Allow',
|
|
226
|
+
Count: 'Allow',
|
|
227
|
+
Schema: 'Allow'
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### Permission Values
|
|
233
|
+
|
|
234
|
+
| Value | Description |
|
|
235
|
+
|-------|-------------|
|
|
236
|
+
| `'Allow'` | Full access to the operation |
|
|
237
|
+
| `'Deny'` | No access to the operation |
|
|
238
|
+
| Custom string | Contextual filter (e.g., `'Mine'`, `'MyCustomer'`) applied by Meadow-Endpoints behaviors |
|
|
239
|
+
|
|
240
|
+
### Operations
|
|
241
|
+
|
|
242
|
+
| Operation | CRUD Method | Description |
|
|
243
|
+
|-----------|-------------|-------------|
|
|
244
|
+
| `Create` | `doCreate` | Insert new records |
|
|
245
|
+
| `Read` | `doRead` | Retrieve a single record |
|
|
246
|
+
| `Reads` | `doReads` | Retrieve multiple records |
|
|
247
|
+
| `ReadsBy` | `doReads` | Retrieve by alternate key |
|
|
248
|
+
| `ReadMax` | `doRead` | Retrieve max value |
|
|
249
|
+
| `ReadSelectList` | `doReads` | Retrieve select lists |
|
|
250
|
+
| `Update` | `doUpdate` | Modify existing records |
|
|
251
|
+
| `Delete` | `doDelete` | Soft delete records |
|
|
252
|
+
| `Count` | `doCount` | Count records |
|
|
253
|
+
| `CountBy` | `doCount` | Count by alternate key |
|
|
254
|
+
| `Schema` | — | Access schema metadata |
|
|
255
|
+
| `Validate` | — | Validate objects |
|
|
256
|
+
| `New` | — | Generate default objects |
|
|
257
|
+
|
|
258
|
+
### Default Role Names
|
|
259
|
+
|
|
260
|
+
Meadow uses a default set of role names (configurable via `MeadowRoleNames` in Fable settings):
|
|
261
|
+
|
|
262
|
+
```javascript
|
|
263
|
+
['Unauthenticated', 'User', 'Manager', 'Director', 'Executive', 'Administrator']
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
## Loading from a Package File
|
|
267
|
+
|
|
268
|
+
The most common way to define a complete schema is through a package JSON file that bundles all four parts together.
|
|
269
|
+
|
|
270
|
+
### Package File Format
|
|
271
|
+
|
|
272
|
+
```json
|
|
273
|
+
{
|
|
274
|
+
"Scope": "Book",
|
|
275
|
+
"Domain": "Library",
|
|
276
|
+
"DefaultIdentifier": "IDBook",
|
|
277
|
+
"Schema": [
|
|
278
|
+
{ "Column": "IDBook", "Type": "AutoIdentity" },
|
|
279
|
+
{ "Column": "GUIDBook", "Type": "AutoGUID" },
|
|
280
|
+
{ "Column": "Title", "Type": "String", "Size": "255" },
|
|
281
|
+
{ "Column": "Author", "Type": "String", "Size": "128" },
|
|
282
|
+
{ "Column": "CreateDate", "Type": "CreateDate" },
|
|
283
|
+
{ "Column": "CreatingIDUser", "Type": "CreateIDUser" },
|
|
284
|
+
{ "Column": "UpdateDate", "Type": "UpdateDate" },
|
|
285
|
+
{ "Column": "UpdatingIDUser", "Type": "UpdateIDUser" },
|
|
286
|
+
{ "Column": "DeleteDate", "Type": "DeleteDate" },
|
|
287
|
+
{ "Column": "DeletingIDUser", "Type": "DeleteIDUser" },
|
|
288
|
+
{ "Column": "Deleted", "Type": "Deleted" }
|
|
289
|
+
],
|
|
290
|
+
"JsonSchema": {
|
|
291
|
+
"title": "Book",
|
|
292
|
+
"type": "object",
|
|
293
|
+
"properties": {
|
|
294
|
+
"IDBook": { "type": "integer" },
|
|
295
|
+
"Title": { "type": "string" },
|
|
296
|
+
"Author": { "type": "string" }
|
|
297
|
+
},
|
|
298
|
+
"required": ["Title"]
|
|
299
|
+
},
|
|
300
|
+
"DefaultObject": {
|
|
301
|
+
"IDBook": null,
|
|
302
|
+
"GUIDBook": "",
|
|
303
|
+
"Title": "Untitled",
|
|
304
|
+
"Author": "Unknown"
|
|
305
|
+
},
|
|
306
|
+
"Authorization": {
|
|
307
|
+
"Unauthenticated": { "Read": "Deny", "Create": "Deny" },
|
|
308
|
+
"User": { "Read": "Allow", "Create": "Allow" },
|
|
309
|
+
"Administrator": { "Read": "Allow", "Create": "Allow" }
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
### Loading from File
|
|
315
|
+
|
|
316
|
+
```javascript
|
|
317
|
+
const meadow = require('meadow')
|
|
318
|
+
.new(libFable)
|
|
319
|
+
.loadFromPackage(__dirname + '/Book.json');
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
The `loadFromPackage` method reads the JSON file and calls:
|
|
323
|
+
- `setScope(package.Scope)`
|
|
324
|
+
- `setDefaultIdentifier(package.DefaultIdentifier)`
|
|
325
|
+
- `setSchema(package.Schema)`
|
|
326
|
+
- `setJsonSchema(package.JsonSchema)`
|
|
327
|
+
- `setDefault(package.DefaultObject)`
|
|
328
|
+
- `setAuthorizer(package.Authorization)`
|
|
329
|
+
|
|
330
|
+
### Loading from Object
|
|
331
|
+
|
|
332
|
+
If you already have the package definition as a JavaScript object:
|
|
333
|
+
|
|
334
|
+
```javascript
|
|
335
|
+
const meadow = require('meadow')
|
|
336
|
+
.new(libFable)
|
|
337
|
+
.loadFromPackageObject({
|
|
338
|
+
Scope: 'Book',
|
|
339
|
+
DefaultIdentifier: 'IDBook',
|
|
340
|
+
Schema:
|
|
341
|
+
[
|
|
342
|
+
{ Column: 'IDBook', Type: 'AutoIdentity' },
|
|
343
|
+
{ Column: 'Title', Type: 'String', Size: '255' }
|
|
344
|
+
],
|
|
345
|
+
DefaultObject:
|
|
346
|
+
{
|
|
347
|
+
IDBook: null,
|
|
348
|
+
Title: 'Untitled'
|
|
349
|
+
}
|
|
350
|
+
});
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
### Package Properties
|
|
354
|
+
|
|
355
|
+
| Property | Required | Description |
|
|
356
|
+
|----------|----------|-------------|
|
|
357
|
+
| `Scope` | Yes | Entity name (e.g., `'Book'`) |
|
|
358
|
+
| `Domain` | No | Domain grouping for organization |
|
|
359
|
+
| `DefaultIdentifier` | Yes | Primary key column name (e.g., `'IDBook'`) |
|
|
360
|
+
| `Schema` | Yes | Column schema array |
|
|
361
|
+
| `JsonSchema` | No | JSON Schema v4 for validation |
|
|
362
|
+
| `DefaultObject` | No | Default values template |
|
|
363
|
+
| `Authorization` | No | Role-based access control rules |
|
|
364
|
+
|
|
365
|
+
## API Reference
|
|
366
|
+
|
|
367
|
+
### Meadow Schema Methods
|
|
368
|
+
|
|
369
|
+
All schema methods are chainable and return the Meadow instance.
|
|
370
|
+
|
|
371
|
+
| Method | Parameters | Description |
|
|
372
|
+
|--------|------------|-------------|
|
|
373
|
+
| `setSchema(pSchema)` | Array of column definitions | Set the column schema |
|
|
374
|
+
| `setJsonSchema(pJsonSchema)` | JSON Schema v4 object | Set validation schema |
|
|
375
|
+
| `setDefault(pDefault)` | Default values object | Set default object template |
|
|
376
|
+
| `setAuthorizer(pAuthorizer)` | Authorization rules object | Set role-based access rules |
|
|
377
|
+
| `loadFromPackage(pPath)` | File path to JSON | Load all schema parts from a file |
|
|
378
|
+
| `loadFromPackageObject(pObject)` | Package object | Load all schema parts from an object |
|
|
379
|
+
| `validateObject(pObject)` | Object to validate | Validate against JSON Schema |
|
|
380
|
+
|
|
381
|
+
### Meadow Schema Properties
|
|
382
|
+
|
|
383
|
+
| Property | Type | Description |
|
|
384
|
+
|----------|------|-------------|
|
|
385
|
+
| `schema` | array | The column definition array |
|
|
386
|
+
| `schemaFull` | MeadowSchema | The full schema object with all methods |
|
|
387
|
+
| `jsonSchema` | object | The JSON Schema v4 definition |
|
|
388
|
+
| `defaultIdentifier` | string | The primary key column name |
|
|
389
|
+
| `defaultGUIdentifier` | string | The GUID column name (derived from identifier) |
|
|
390
|
+
|
|
391
|
+
## Provider Synchronization
|
|
392
|
+
|
|
393
|
+
When you call `setSchema()`, Meadow automatically synchronizes the schema with the active provider. This is important for providers like ALASQL that create tables dynamically based on the schema definition.
|
|
394
|
+
|
|
395
|
+
```javascript
|
|
396
|
+
// Internally, Meadow calls:
|
|
397
|
+
// provider.setSchema(scope, schema, defaultIdentifier, defaultGUIdentifier)
|
|
398
|
+
|
|
399
|
+
// This means you can change schemas at runtime:
|
|
400
|
+
meadow.setSchema(updatedSchema);
|
|
401
|
+
// The provider is automatically updated
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
## Related Documentation
|
|
405
|
+
|
|
406
|
+
- [Query Overview](query/README.md) — How queries interact with schema-defined columns
|
|
407
|
+
- [Providers Overview](providers/README.md) — How providers use schema for SQL generation
|
|
408
|
+
- [Create Operation](query/create.md) — How default objects and auto-stamps work during create
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "meadow",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.18",
|
|
4
4
|
"description": "A data access library.",
|
|
5
5
|
"main": "source/Meadow.js",
|
|
6
6
|
"scripts": {
|
|
@@ -11,7 +11,18 @@
|
|
|
11
11
|
"build": "./node_modules/.bin/gulp build --full-paths",
|
|
12
12
|
"docker-dev-build": "docker build ./ -f Dockerfile_LUXURYCode -t retold/meadow:local",
|
|
13
13
|
"docker-dev-run": "docker run -it -d --name meadow-dev -p 12342:8080 -p 12106:3306 -v \"$PWD/.config:/home/coder/.config\" -v \"$PWD:/home/coder/meadow\" -u \"$(id -u):$(id -g)\" -e \"DOCKER_USER=$USER\" retold/meadow:local",
|
|
14
|
-
"docker-dev-shell": "docker exec -it meadow-dev /bin/bash"
|
|
14
|
+
"docker-dev-shell": "docker exec -it meadow-dev /bin/bash",
|
|
15
|
+
"docker-mysql-start": "./scripts/mysql-test-db.sh start",
|
|
16
|
+
"docker-mysql-stop": "./scripts/mysql-test-db.sh stop",
|
|
17
|
+
"docker-mysql-status": "./scripts/mysql-test-db.sh status",
|
|
18
|
+
"test-mysql": "./scripts/mysql-test-db.sh start && ./node_modules/mocha/bin/_mocha -u tdd --exit -R spec --grep Meadow-Provider-MySQL",
|
|
19
|
+
"test-mysql-all": "./scripts/mysql-test-db.sh start && ./node_modules/mocha/bin/_mocha -u tdd --exit -R spec",
|
|
20
|
+
"docker-mssql-start": "./scripts/mssql-test-db.sh start",
|
|
21
|
+
"docker-mssql-stop": "./scripts/mssql-test-db.sh stop",
|
|
22
|
+
"docker-mssql-status": "./scripts/mssql-test-db.sh status",
|
|
23
|
+
"test-mssql": "./scripts/mssql-test-db.sh start && ./node_modules/mocha/bin/_mocha -u tdd --exit -R spec --grep Meadow-Provider-MSSQL",
|
|
24
|
+
"test-mssql-all": "./scripts/mssql-test-db.sh start && ./node_modules/mocha/bin/_mocha -u tdd --exit -R spec",
|
|
25
|
+
"test-all-providers": "./scripts/mysql-test-db.sh start && ./scripts/mssql-test-db.sh start && ./node_modules/mocha/bin/_mocha -u tdd --exit -R spec"
|
|
15
26
|
},
|
|
16
27
|
"mocha": {
|
|
17
28
|
"diff": true,
|
|
@@ -47,9 +58,10 @@
|
|
|
47
58
|
"homepage": "https://github.com/stevenvelozo/meadow",
|
|
48
59
|
"devDependencies": {
|
|
49
60
|
"alasql": "^4.1.10",
|
|
61
|
+
"better-sqlite3": "^12.6.2",
|
|
50
62
|
"browserify": "^17.0.0",
|
|
51
63
|
"chai": "4.3.10",
|
|
52
|
-
"fable": "^3.1.
|
|
64
|
+
"fable": "^3.1.51",
|
|
53
65
|
"gulp": "^4.0.2",
|
|
54
66
|
"gulp-babel": "^8.0.0",
|
|
55
67
|
"gulp-sourcemaps": "^3.0.0",
|
|
@@ -57,6 +69,7 @@
|
|
|
57
69
|
"gulp-util": "^3.0.8",
|
|
58
70
|
"meadow-connection-mssql": "^1.0.9",
|
|
59
71
|
"meadow-connection-mysql": "^1.0.4",
|
|
72
|
+
"meadow-connection-sqlite": "file:../meadow-connection-sqlite",
|
|
60
73
|
"mocha": "10.2.0",
|
|
61
74
|
"mysql2": "^3.6.3",
|
|
62
75
|
"nyc": "^15.1.0",
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# MSSQL Test Database Management Script
|
|
3
|
+
#
|
|
4
|
+
# Usage:
|
|
5
|
+
# ./scripts/mssql-test-db.sh start - Start MSSQL container and wait for readiness
|
|
6
|
+
# ./scripts/mssql-test-db.sh stop - Stop and remove the container
|
|
7
|
+
# ./scripts/mssql-test-db.sh status - Check if the container is running
|
|
8
|
+
#
|
|
9
|
+
# The container settings match the test configuration in
|
|
10
|
+
# test/Meadow-Provider-MSSQL_tests.js:
|
|
11
|
+
# Host: 127.0.0.1, Port: 14333, User: sa
|
|
12
|
+
# Password: 1234567890abc., Database: bookstore
|
|
13
|
+
|
|
14
|
+
CONTAINER_NAME="meadow-mssql-test"
|
|
15
|
+
SA_PASSWORD="1234567890abc."
|
|
16
|
+
MSSQL_DATABASE="bookstore"
|
|
17
|
+
MSSQL_PORT="14333"
|
|
18
|
+
MSSQL_IMAGE="mcr.microsoft.com/mssql/server:2022-latest"
|
|
19
|
+
|
|
20
|
+
start_mssql() {
|
|
21
|
+
# Check if container already exists
|
|
22
|
+
if docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
|
|
23
|
+
if docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
|
|
24
|
+
echo "MSSQL test container is already running."
|
|
25
|
+
return 0
|
|
26
|
+
else
|
|
27
|
+
echo "Removing stopped container..."
|
|
28
|
+
docker rm "${CONTAINER_NAME}" > /dev/null 2>&1
|
|
29
|
+
fi
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
echo "Starting MSSQL test container..."
|
|
33
|
+
docker run -d \
|
|
34
|
+
--name "${CONTAINER_NAME}" \
|
|
35
|
+
-e ACCEPT_EULA=Y \
|
|
36
|
+
-e "MSSQL_SA_PASSWORD=${SA_PASSWORD}" \
|
|
37
|
+
-p "${MSSQL_PORT}:1433" \
|
|
38
|
+
"${MSSQL_IMAGE}"
|
|
39
|
+
|
|
40
|
+
if [ $? -ne 0 ]; then
|
|
41
|
+
echo "ERROR: Failed to start MSSQL container."
|
|
42
|
+
exit 1
|
|
43
|
+
fi
|
|
44
|
+
|
|
45
|
+
echo "Waiting for MSSQL to be ready..."
|
|
46
|
+
RETRIES=30
|
|
47
|
+
until docker exec "${CONTAINER_NAME}" /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P "${SA_PASSWORD}" -C -Q "SELECT 1" > /dev/null 2>&1; do
|
|
48
|
+
RETRIES=$((RETRIES - 1))
|
|
49
|
+
if [ $RETRIES -le 0 ]; then
|
|
50
|
+
echo "ERROR: MSSQL failed to become ready in time."
|
|
51
|
+
docker logs "${CONTAINER_NAME}" 2>&1 | tail -20
|
|
52
|
+
exit 1
|
|
53
|
+
fi
|
|
54
|
+
echo " ...waiting (${RETRIES} retries left)"
|
|
55
|
+
sleep 2
|
|
56
|
+
done
|
|
57
|
+
|
|
58
|
+
# Create the bookstore database
|
|
59
|
+
echo "Creating database '${MSSQL_DATABASE}'..."
|
|
60
|
+
docker exec "${CONTAINER_NAME}" /opt/mssql-tools18/bin/sqlcmd \
|
|
61
|
+
-S localhost -U sa -P "${SA_PASSWORD}" -C \
|
|
62
|
+
-Q "IF NOT EXISTS(SELECT * FROM sys.databases WHERE name = '${MSSQL_DATABASE}') BEGIN CREATE DATABASE [${MSSQL_DATABASE}] END"
|
|
63
|
+
|
|
64
|
+
echo ""
|
|
65
|
+
echo "MSSQL test database is ready!"
|
|
66
|
+
echo " Container: ${CONTAINER_NAME}"
|
|
67
|
+
echo " Host: 127.0.0.1:${MSSQL_PORT}"
|
|
68
|
+
echo " User: sa"
|
|
69
|
+
echo " Password: ${SA_PASSWORD}"
|
|
70
|
+
echo " Database: ${MSSQL_DATABASE}"
|
|
71
|
+
echo ""
|
|
72
|
+
echo "Run tests with: npm test"
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
stop_mssql() {
|
|
76
|
+
if docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
|
|
77
|
+
echo "Stopping and removing MSSQL test container..."
|
|
78
|
+
docker stop "${CONTAINER_NAME}" > /dev/null 2>&1
|
|
79
|
+
docker rm "${CONTAINER_NAME}" > /dev/null 2>&1
|
|
80
|
+
echo "MSSQL test container removed."
|
|
81
|
+
else
|
|
82
|
+
echo "No MSSQL test container found."
|
|
83
|
+
fi
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
status_mssql() {
|
|
87
|
+
if docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
|
|
88
|
+
echo "MSSQL test container is running."
|
|
89
|
+
docker ps --filter "name=${CONTAINER_NAME}" --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
|
|
90
|
+
elif docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
|
|
91
|
+
echo "MSSQL test container exists but is stopped."
|
|
92
|
+
else
|
|
93
|
+
echo "MSSQL test container is not running."
|
|
94
|
+
fi
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
case "${1}" in
|
|
98
|
+
start)
|
|
99
|
+
start_mssql
|
|
100
|
+
;;
|
|
101
|
+
stop)
|
|
102
|
+
stop_mssql
|
|
103
|
+
;;
|
|
104
|
+
status)
|
|
105
|
+
status_mssql
|
|
106
|
+
;;
|
|
107
|
+
*)
|
|
108
|
+
echo "Usage: $0 {start|stop|status}"
|
|
109
|
+
exit 1
|
|
110
|
+
;;
|
|
111
|
+
esac
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# MySQL Test Database Management Script
|
|
3
|
+
#
|
|
4
|
+
# Usage:
|
|
5
|
+
# ./scripts/mysql-test-db.sh start - Start MySQL container and wait for readiness
|
|
6
|
+
# ./scripts/mysql-test-db.sh stop - Stop and remove the container
|
|
7
|
+
# ./scripts/mysql-test-db.sh status - Check if the container is running
|
|
8
|
+
#
|
|
9
|
+
# The container settings match the test configuration in
|
|
10
|
+
# test/Meadow-Provider-MySQL_tests.js:
|
|
11
|
+
# Host: localhost, Port: 3306, User: root
|
|
12
|
+
# Password: 123456789, Database: bookstore
|
|
13
|
+
|
|
14
|
+
CONTAINER_NAME="meadow-mysql-test"
|
|
15
|
+
MYSQL_ROOT_PASSWORD="123456789"
|
|
16
|
+
MYSQL_DATABASE="bookstore"
|
|
17
|
+
MYSQL_PORT="3306"
|
|
18
|
+
MYSQL_IMAGE="mysql:8.0"
|
|
19
|
+
|
|
20
|
+
start_mysql() {
|
|
21
|
+
# Check if container already exists
|
|
22
|
+
if docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
|
|
23
|
+
if docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
|
|
24
|
+
echo "MySQL test container is already running."
|
|
25
|
+
return 0
|
|
26
|
+
else
|
|
27
|
+
echo "Removing stopped container..."
|
|
28
|
+
docker rm "${CONTAINER_NAME}" > /dev/null 2>&1
|
|
29
|
+
fi
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
echo "Starting MySQL test container..."
|
|
33
|
+
docker run -d \
|
|
34
|
+
--name "${CONTAINER_NAME}" \
|
|
35
|
+
-e MYSQL_ROOT_PASSWORD="${MYSQL_ROOT_PASSWORD}" \
|
|
36
|
+
-e MYSQL_DATABASE="${MYSQL_DATABASE}" \
|
|
37
|
+
-p "${MYSQL_PORT}:3306" \
|
|
38
|
+
"${MYSQL_IMAGE}" \
|
|
39
|
+
--default-authentication-plugin=mysql_native_password \
|
|
40
|
+
--character-set-server=utf8mb4 \
|
|
41
|
+
--collation-server=utf8mb4_unicode_ci
|
|
42
|
+
|
|
43
|
+
if [ $? -ne 0 ]; then
|
|
44
|
+
echo "ERROR: Failed to start MySQL container."
|
|
45
|
+
exit 1
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
echo "Waiting for MySQL to be ready..."
|
|
49
|
+
RETRIES=30
|
|
50
|
+
until docker exec "${CONTAINER_NAME}" mysqladmin ping -h localhost -u root -p"${MYSQL_ROOT_PASSWORD}" --silent 2>/dev/null; do
|
|
51
|
+
RETRIES=$((RETRIES - 1))
|
|
52
|
+
if [ $RETRIES -le 0 ]; then
|
|
53
|
+
echo "ERROR: MySQL failed to become ready in time."
|
|
54
|
+
docker logs "${CONTAINER_NAME}" 2>&1 | tail -20
|
|
55
|
+
exit 1
|
|
56
|
+
fi
|
|
57
|
+
echo " ...waiting (${RETRIES} retries left)"
|
|
58
|
+
sleep 2
|
|
59
|
+
done
|
|
60
|
+
|
|
61
|
+
echo ""
|
|
62
|
+
echo "MySQL test database is ready!"
|
|
63
|
+
echo " Container: ${CONTAINER_NAME}"
|
|
64
|
+
echo " Host: localhost:${MYSQL_PORT}"
|
|
65
|
+
echo " User: root"
|
|
66
|
+
echo " Password: ${MYSQL_ROOT_PASSWORD}"
|
|
67
|
+
echo " Database: ${MYSQL_DATABASE}"
|
|
68
|
+
echo ""
|
|
69
|
+
echo "Run tests with: npm test"
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
stop_mysql() {
|
|
73
|
+
if docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
|
|
74
|
+
echo "Stopping and removing MySQL test container..."
|
|
75
|
+
docker stop "${CONTAINER_NAME}" > /dev/null 2>&1
|
|
76
|
+
docker rm "${CONTAINER_NAME}" > /dev/null 2>&1
|
|
77
|
+
echo "MySQL test container removed."
|
|
78
|
+
else
|
|
79
|
+
echo "No MySQL test container found."
|
|
80
|
+
fi
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
status_mysql() {
|
|
84
|
+
if docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
|
|
85
|
+
echo "MySQL test container is running."
|
|
86
|
+
docker ps --filter "name=${CONTAINER_NAME}" --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
|
|
87
|
+
elif docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
|
|
88
|
+
echo "MySQL test container exists but is stopped."
|
|
89
|
+
else
|
|
90
|
+
echo "MySQL test container is not running."
|
|
91
|
+
fi
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
case "${1}" in
|
|
95
|
+
start)
|
|
96
|
+
start_mysql
|
|
97
|
+
;;
|
|
98
|
+
stop)
|
|
99
|
+
stop_mysql
|
|
100
|
+
;;
|
|
101
|
+
status)
|
|
102
|
+
status_mysql
|
|
103
|
+
;;
|
|
104
|
+
*)
|
|
105
|
+
echo "Usage: $0 {start|stop|status}"
|
|
106
|
+
exit 1
|
|
107
|
+
;;
|
|
108
|
+
esac
|
package/source/Meadow.js
CHANGED
|
@@ -136,6 +136,7 @@ var Meadow = function()
|
|
|
136
136
|
'MeadowEndpoints': require(`./providers/Meadow-Provider-MeadowEndpoints.js`),
|
|
137
137
|
'MySQL': require(`./providers/Meadow-Provider-MySQL.js`),
|
|
138
138
|
'MSSQL': require(`./providers/Meadow-Provider-MSSQL.js`),
|
|
139
|
+
'SQLite': require(`./providers/Meadow-Provider-SQLite.js`),
|
|
139
140
|
'None': require(`./providers/Meadow-Provider-None.js`),
|
|
140
141
|
});
|
|
141
142
|
var setProvider = function(pProviderName)
|