retold-data-service 2.0.8 → 2.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.
@@ -0,0 +1,111 @@
1
+ # Behavior Injection
2
+
3
+ Behavior injection lets you add custom logic before and after any CRUD operation on your endpoints.
4
+
5
+ ## How It Works
6
+
7
+ Each entity's Meadow Endpoints controller has a `BehaviorInjection` object that accepts pre- and post-operation hooks. These hooks run inline with the request handling pipeline.
8
+
9
+ ## Setting a Behavior
10
+
11
+ ```javascript
12
+ _Fable.MeadowEndpoints.Book.controller.BehaviorInjection.setBehavior(
13
+ 'Read-PreOperation',
14
+ (pRequest, pRequestState, fRequestComplete) =>
15
+ {
16
+ // Custom logic before the Read operation
17
+ console.log('About to read Book ID:', pRequest.params.IDRecord);
18
+ return fRequestComplete(false);
19
+ });
20
+ ```
21
+
22
+ ## Available Hook Points
23
+
24
+ Each CRUD operation supports `PreOperation` and `PostOperation` hooks:
25
+
26
+ | Hook Name | Fires |
27
+ |-----------|-------|
28
+ | `Create-PreOperation` | Before a CREATE |
29
+ | `Create-PostOperation` | After a CREATE |
30
+ | `Read-PreOperation` | Before a single READ |
31
+ | `Read-PostOperation` | After a single READ |
32
+ | `Reads-PreOperation` | Before a READ-many |
33
+ | `Reads-PostOperation` | After a READ-many |
34
+ | `Update-PreOperation` | Before an UPDATE |
35
+ | `Update-PostOperation` | After an UPDATE |
36
+ | `Delete-PreOperation` | Before a DELETE |
37
+ | `Delete-PostOperation` | After a DELETE |
38
+ | `Count-PreOperation` | Before a COUNT |
39
+ | `Count-PostOperation` | After a COUNT |
40
+ | `Schema-PreOperation` | Before a SCHEMA read |
41
+ | `Schema-PostOperation` | After a SCHEMA read |
42
+
43
+ ## Request State
44
+
45
+ The `pRequestState` object contains:
46
+
47
+ | Property | Description |
48
+ |----------|-------------|
49
+ | `Record` | The current record being operated on |
50
+ | `RecordToCreate` | The record being created (Create operations) |
51
+ | `Query` | The FoxHound query object |
52
+ | `SessionData` | Session data for the current request |
53
+
54
+ ## Examples
55
+
56
+ ### Enriching Read Results
57
+
58
+ ```javascript
59
+ _Fable.MeadowEndpoints.Book.controller.BehaviorInjection.setBehavior(
60
+ 'Read-PostOperation',
61
+ (pRequest, pRequestState, fRequestComplete) =>
62
+ {
63
+ // Load related author data
64
+ let tmpQuery = _Fable.DAL.BookAuthorJoin.query
65
+ .addFilter('IDBook', pRequestState.Record.IDBook);
66
+
67
+ _Fable.DAL.BookAuthorJoin.doReads(tmpQuery,
68
+ (pError, pQuery, pJoinRecords) =>
69
+ {
70
+ pRequestState.Record.AuthorJoins = pJoinRecords;
71
+ return fRequestComplete(false);
72
+ });
73
+ });
74
+ ```
75
+
76
+ ### Validating Before Create
77
+
78
+ ```javascript
79
+ _Fable.MeadowEndpoints.Book.controller.BehaviorInjection.setBehavior(
80
+ 'Create-PreOperation',
81
+ (pRequest, pRequestState, fRequestComplete) =>
82
+ {
83
+ if (!pRequestState.RecordToCreate.Title)
84
+ {
85
+ pRequest.CommonServices.sendCodedResponse(
86
+ pRequestState.response, 400, 'Title is required');
87
+ return fRequestComplete(true); // true signals an error
88
+ }
89
+ return fRequestComplete(false);
90
+ });
91
+ ```
92
+
93
+ ### Adding Query Filters
94
+
95
+ ```javascript
96
+ _Fable.MeadowEndpoints.Book.controller.BehaviorInjection.setBehavior(
97
+ 'Reads-PreOperation',
98
+ (pRequest, pRequestState, fRequestComplete) =>
99
+ {
100
+ // Only return English-language books
101
+ pRequestState.Query.addFilter('Language', 'English');
102
+ return fRequestComplete(false);
103
+ });
104
+ ```
105
+
106
+ ## Removing a Behavior
107
+
108
+ ```javascript
109
+ delete _Fable.MeadowEndpoints.Book.controller
110
+ .BehaviorInjection._BehaviorFunctions['Read-PreOperation'];
111
+ ```
@@ -0,0 +1,93 @@
1
+ # Configuration
2
+
3
+ Retold Data Service accepts options during instantiation that control model loading, storage provider selection, and server behavior.
4
+
5
+ ## Options Reference
6
+
7
+ | Option | Type | Default | Description |
8
+ |--------|------|---------|-------------|
9
+ | `StorageProvider` | String | `'MySQL'` | Meadow provider name (`MySQL`, `SQLite`, `MSSQL`, `ALASQL`) |
10
+ | `StorageProviderModule` | String | `'meadow-connection-mysql'` | npm module name for the storage provider |
11
+ | `FullMeadowSchemaPath` | String | `${process.cwd()}/model/` | Directory path where the compiled schema lives |
12
+ | `FullMeadowSchemaFilename` | String | `'MeadowModel-Extended.json'` | Filename of the compiled Stricture schema |
13
+ | `AutoInitializeDataService` | Boolean | `true` | Whether to auto-initialize on construction |
14
+ | `AutoStartOrator` | Boolean | `true` | Whether to start the Orator web server during initialization |
15
+
16
+ ## Fable Settings
17
+
18
+ The Fable instance configuration controls server port and database connection:
19
+
20
+ ```javascript
21
+ {
22
+ Product: 'MyApp',
23
+ APIServerPort: 8086,
24
+
25
+ // For MySQL
26
+ MySQL: {
27
+ Server: '127.0.0.1',
28
+ Port: 3306,
29
+ User: 'root',
30
+ Password: 'secret',
31
+ Database: 'mydb',
32
+ ConnectionPoolLimit: 20
33
+ },
34
+ MeadowConnectionMySQLAutoConnect: true,
35
+
36
+ // For SQLite
37
+ SQLite: {
38
+ SQLiteFilePath: './data/myapp.db'
39
+ }
40
+ }
41
+ ```
42
+
43
+ ## Example: MySQL Configuration
44
+
45
+ ```javascript
46
+ _Fable.serviceManager.instantiateServiceProvider('RetoldDataService', {
47
+ FullMeadowSchemaPath: `${__dirname}/model/`,
48
+ FullMeadowSchemaFilename: 'MeadowModel-Extended.json',
49
+ StorageProvider: 'MySQL',
50
+ StorageProviderModule: 'meadow-connection-mysql',
51
+ AutoStartOrator: true
52
+ });
53
+ ```
54
+
55
+ ## Example: SQLite Configuration
56
+
57
+ ```javascript
58
+ // Register the SQLite provider before creating the data service
59
+ const libMeadowConnectionSQLite = require('meadow-connection-sqlite');
60
+ _Fable.serviceManager.addServiceType('MeadowSQLiteProvider', libMeadowConnectionSQLite);
61
+ _Fable.serviceManager.instantiateServiceProvider('MeadowSQLiteProvider');
62
+
63
+ _Fable.MeadowSQLiteProvider.connectAsync(
64
+ (pError) =>
65
+ {
66
+ _Fable.serviceManager.instantiateServiceProvider('RetoldDataService', {
67
+ FullMeadowSchemaPath: `${__dirname}/model/`,
68
+ FullMeadowSchemaFilename: 'MeadowModel-Extended.json',
69
+ StorageProvider: 'SQLite',
70
+ StorageProviderModule: 'meadow-connection-sqlite',
71
+ AutoStartOrator: true
72
+ });
73
+ });
74
+ ```
75
+
76
+ ## Example: Disabling Auto-Start
77
+
78
+ ```javascript
79
+ _Fable.serviceManager.instantiateServiceProvider('RetoldDataService', {
80
+ FullMeadowSchemaPath: `${__dirname}/model/`,
81
+ FullMeadowSchemaFilename: 'MeadowModel-Extended.json',
82
+ AutoStartOrator: false
83
+ });
84
+
85
+ // Manually start later
86
+ _Fable.RetoldDataService.initializeService(
87
+ (pError) =>
88
+ {
89
+ // Service initialized without starting the web server
90
+ // Start it manually when ready:
91
+ _Fable.Orator.startService(() => console.log('Server started'));
92
+ });
93
+ ```
package/docs/cover.md ADDED
@@ -0,0 +1,11 @@
1
+ # Retold Data Service <small>2</small>
2
+
3
+ > All-in-one data service for the Retold ecosystem
4
+
5
+ - Schema-driven REST API generation from Stricture DDL models
6
+ - Automatic CRUD endpoints for every entity in your data model
7
+ - Pluggable storage providers: MySQL, SQLite, MSSQL, ALASQL
8
+ - Built on Fable, Meadow, and Orator with lifecycle hooks for customization
9
+
10
+ [GitHub](https://github.com/stevenvelozo/retold-data-service)
11
+ [Get Started](#retold-data-service)
@@ -0,0 +1,87 @@
1
+ # DAL Access
2
+
3
+ In addition to the REST endpoints, you can query your data directly through the Meadow DAL objects.
4
+
5
+ ## Accessing DAL Objects
6
+
7
+ After initialization, every entity in your model has a DAL object accessible via `fable.DAL`:
8
+
9
+ ```javascript
10
+ // Read a single record
11
+ let tmpQuery = _Fable.DAL.Book.query.addFilter('IDBook', 42);
12
+
13
+ _Fable.DAL.Book.doRead(tmpQuery,
14
+ (pError, pQuery, pRecord) =>
15
+ {
16
+ console.log('Found:', pRecord.Title);
17
+ });
18
+ ```
19
+
20
+ ## Available Operations
21
+
22
+ | Method | Description |
23
+ |--------|-------------|
24
+ | `doCreate(query, callback)` | Insert a new record |
25
+ | `doRead(query, callback)` | Read a single record |
26
+ | `doReads(query, callback)` | Read multiple records |
27
+ | `doUpdate(query, callback)` | Update an existing record |
28
+ | `doDelete(query, callback)` | Soft-delete a record |
29
+ | `doUndelete(query, callback)` | Restore a soft-deleted record |
30
+ | `doCount(query, callback)` | Count matching records |
31
+
32
+ ## Query Building
33
+
34
+ Each DAL has a `query` property (a FoxHound query instance) that you can configure with filters, sorting, and pagination:
35
+
36
+ ```javascript
37
+ // Read all Science Fiction books, newest first, page 1
38
+ let tmpQuery = _Fable.DAL.Book.query
39
+ .addFilter('Genre', 'Science Fiction')
40
+ .addSort({Column: 'PublicationYear', Direction: 'Descending'})
41
+ .setCap(25)
42
+ .setBegin(0);
43
+
44
+ _Fable.DAL.Book.doReads(tmpQuery,
45
+ (pError, pQuery, pRecords) =>
46
+ {
47
+ console.log(`Found ${pRecords.length} books`);
48
+ });
49
+ ```
50
+
51
+ ## Creating Records
52
+
53
+ ```javascript
54
+ let tmpQuery = _Fable.DAL.Author.query
55
+ .addRecord({Name: 'Frank Herbert'});
56
+
57
+ _Fable.DAL.Author.doCreate(tmpQuery,
58
+ (pError, pCreateQuery, pReadQuery, pRecord) =>
59
+ {
60
+ console.log('Created author with ID:', pRecord.IDAuthor);
61
+ });
62
+ ```
63
+
64
+ ## Counting Records
65
+
66
+ ```javascript
67
+ let tmpQuery = _Fable.DAL.Review.query;
68
+
69
+ _Fable.DAL.Review.doCount(tmpQuery,
70
+ (pError, pQuery, pCount) =>
71
+ {
72
+ console.log('Total reviews:', pCount);
73
+ });
74
+ ```
75
+
76
+ ## When to Use DAL vs Endpoints
77
+
78
+ Use the **REST endpoints** when:
79
+ - Building a web UI that communicates over HTTP
80
+ - Exposing an API for external consumers
81
+ - You need session management and authorization
82
+
83
+ Use **DAL access** when:
84
+ - Running server-side business logic
85
+ - Performing batch operations
86
+ - Writing behavior injection hooks that need to query related entities
87
+ - Running background tasks or workers
@@ -0,0 +1,121 @@
1
+ # Endpoints
2
+
3
+ Once initialized, Retold Data Service automatically creates RESTful endpoints for every entity in your Stricture model.
4
+
5
+ ## URL Pattern
6
+
7
+ All endpoints follow the Meadow Endpoints URL pattern:
8
+
9
+ ```
10
+ /{Version}/{Entity} — singular entity operations (Read, Create, Update, Delete)
11
+ /{Version}/{Entity}s — plural entity operations (Reads, Count)
12
+ ```
13
+
14
+ The version defaults to `1.0`.
15
+
16
+ ## Available Endpoints
17
+
18
+ For an entity named `Book`, these endpoints are created:
19
+
20
+ ### Read (Single Record)
21
+
22
+ ```
23
+ GET /1.0/Book/:IDRecord
24
+ ```
25
+
26
+ Returns a single record by primary key. Returns a 404 error object if not found.
27
+
28
+ ### Read (Multiple Records)
29
+
30
+ ```
31
+ GET /1.0/Books
32
+ GET /1.0/Books/:Begin/:Cap
33
+ GET /1.0/Books/FilteredTo/:Filter
34
+ GET /1.0/Books/FilteredTo/:Filter/:Begin/:Cap
35
+ ```
36
+
37
+ Returns an array of records. Supports pagination via `Begin` and `Cap` parameters, and filtering via the Meadow filter stanza format.
38
+
39
+ ### Create
40
+
41
+ ```
42
+ POST /1.0/Book
43
+ Body: { "Title": "Dune", "Genre": "Science Fiction" }
44
+ ```
45
+
46
+ Creates a new record and returns the created record (with auto-generated ID and GUID).
47
+
48
+ ### Update
49
+
50
+ ```
51
+ PUT /1.0/Book
52
+ Body: { "IDBook": 1, "Title": "Dune (Updated)" }
53
+ ```
54
+
55
+ Updates an existing record and returns the updated record. The request body must include the primary key.
56
+
57
+ ### Delete (Soft Delete)
58
+
59
+ ```
60
+ DELETE /1.0/Book/:IDRecord
61
+ ```
62
+
63
+ Soft-deletes a record (sets `Deleted = 1`). Returns the number of affected records.
64
+
65
+ ### Count
66
+
67
+ ```
68
+ GET /1.0/Books/Count
69
+ GET /1.0/Books/Count/FilteredTo/:Filter
70
+ GET /1.0/Books/Count/By/:ByField/:ByValue
71
+ ```
72
+
73
+ Returns the count of matching records as `{ "Count": N }`.
74
+
75
+ ### Schema
76
+
77
+ ```
78
+ GET /1.0/Book/Schema
79
+ ```
80
+
81
+ Returns the JSON Schema for the entity.
82
+
83
+ ### New Default Record
84
+
85
+ ```
86
+ GET /1.0/Book/Schema/New
87
+ ```
88
+
89
+ Returns a new default record with all fields set to their default values.
90
+
91
+ ## Filter Stanza Format
92
+
93
+ Filters use the Meadow filter stanza format in the URL:
94
+
95
+ ```
96
+ FBV~Column~Operator~Value
97
+ ```
98
+
99
+ | Code | Meaning |
100
+ |------|---------|
101
+ | `FBV` | Filter By Value |
102
+ | `EQ` | Equals |
103
+ | `NE` | Not Equals |
104
+ | `GT` | Greater Than |
105
+ | `GE` | Greater Than or Equal |
106
+ | `LT` | Less Than |
107
+ | `LE` | Less Than or Equal |
108
+ | `LK` | LIKE |
109
+
110
+ ### Examples
111
+
112
+ ```
113
+ # Filter by exact genre
114
+ GET /1.0/Books/FilteredTo/FBV~Genre~EQ~Science Fiction
115
+
116
+ # Filter by title pattern (URL-encode the % as %25)
117
+ GET /1.0/Books/FilteredTo/FBV~Title~LK~%25Dune%25
118
+
119
+ # Count filtered records
120
+ GET /1.0/Books/Count/FilteredTo/FBV~Genre~EQ~Fantasy
121
+ ```
@@ -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="https://cdn.jsdelivr.net/npm/pict-docuserve@0/dist/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>
@@ -0,0 +1,77 @@
1
+ # Initialization
2
+
3
+ Retold Data Service follows an ordered initialization sequence. Understanding this sequence is key to customizing the service behavior.
4
+
5
+ ## Basic Initialization
6
+
7
+ ```javascript
8
+ const libFable = require('fable');
9
+ const libRetoldDataService = require('retold-data-service');
10
+
11
+ const _Fable = new libFable(settings);
12
+
13
+ // 1. Register the service type
14
+ _Fable.serviceManager.addServiceType('RetoldDataService', libRetoldDataService);
15
+
16
+ // 2. Instantiate the service with options
17
+ _Fable.serviceManager.instantiateServiceProvider('RetoldDataService', options);
18
+
19
+ // 3. Initialize the service
20
+ _Fable.RetoldDataService.initializeService(
21
+ (pError) =>
22
+ {
23
+ if (pError) return console.error('Init failed:', pError);
24
+ console.log('Ready!');
25
+ });
26
+ ```
27
+
28
+ ## Initialization Sequence
29
+
30
+ When `initializeService()` is called, the following steps run in order:
31
+
32
+ ```
33
+ 1. onBeforeInitialize() ← your custom hook
34
+ 2. Start Orator web server ← if AutoStartOrator is true
35
+ 3. initializePersistenceEngine() ← loads the storage provider module
36
+ 4. onInitialize() ← your custom hook
37
+ 5. initializeDataEndpoints() ← loads model, creates DAL + endpoints
38
+ 6. onAfterInitialize() ← your custom hook
39
+ ```
40
+
41
+ ### Step 5: initializeDataEndpoints()
42
+
43
+ This is the core step where the model is loaded and endpoints are created:
44
+
45
+ 1. Load the compiled Stricture schema from `FullMeadowSchemaPath + FullMeadowSchemaFilename`
46
+ 2. Extract the list of table names from `model.Tables`
47
+ 3. For each table:
48
+ - Create a Meadow DAL from the table's `MeadowSchema` package
49
+ - Set the DAL's provider to the configured `StorageProvider`
50
+ - Create a Meadow Endpoints controller for the DAL
51
+ - Connect the endpoint routes to the Orator service server
52
+
53
+ ## Double-Initialization Protection
54
+
55
+ Calling `initializeService()` a second time returns an error:
56
+
57
+ ```javascript
58
+ _Fable.RetoldDataService.initializeService(
59
+ (pError) =>
60
+ {
61
+ // pError.message: "Retold Data Service Application is being
62
+ // initialized but has already been initialized..."
63
+ });
64
+ ```
65
+
66
+ ## Stopping the Service
67
+
68
+ ```javascript
69
+ _Fable.RetoldDataService.stopService(
70
+ (pError) =>
71
+ {
72
+ if (pError) return console.error('Stop failed:', pError);
73
+ console.log('Service stopped');
74
+ });
75
+ ```
76
+
77
+ Calling `stopService()` on an uninitialized service returns an error.
@@ -0,0 +1,81 @@
1
+ # Lifecycle Hooks
2
+
3
+ Retold Data Service provides three lifecycle hooks that you can override to customize initialization behavior.
4
+
5
+ ## Available Hooks
6
+
7
+ | Hook | Fires | Use Case |
8
+ |------|-------|----------|
9
+ | `onBeforeInitialize(fCallback)` | Before Orator starts and before the persistence engine loads | Environment checks, early configuration |
10
+ | `onInitialize(fCallback)` | After Orator starts and persistence engine loads, before data endpoints are created | Register additional services, run migrations |
11
+ | `onAfterInitialize(fCallback)` | After all data endpoints are created and routes are connected | Post-startup tasks, inject behaviors, warm caches |
12
+
13
+ ## Overriding Hooks
14
+
15
+ You can override the hooks by subclassing `RetoldDataService`:
16
+
17
+ ```javascript
18
+ const libRetoldDataService = require('retold-data-service');
19
+
20
+ class MyDataService extends libRetoldDataService
21
+ {
22
+ onBeforeInitialize(fCallback)
23
+ {
24
+ this.fable.log.info('Custom pre-initialization...');
25
+ // Perform environment validation, load configs, etc.
26
+ return fCallback();
27
+ }
28
+
29
+ onInitialize(fCallback)
30
+ {
31
+ this.fable.log.info('Custom initialization...');
32
+ // Run database migrations, register extra services, etc.
33
+ return fCallback();
34
+ }
35
+
36
+ onAfterInitialize(fCallback)
37
+ {
38
+ this.fable.log.info('Custom post-initialization...');
39
+ // Inject behaviors, seed data, start background tasks, etc.
40
+ this.fable.MeadowEndpoints.Book.controller.BehaviorInjection
41
+ .setBehavior('Read-PostOperation', myCustomHook);
42
+ return fCallback();
43
+ }
44
+ }
45
+ ```
46
+
47
+ ## Hook Execution Order
48
+
49
+ ```
50
+ initializeService() called
51
+
52
+ ├── onBeforeInitialize()
53
+
54
+ ├── Start Orator (if AutoStartOrator)
55
+
56
+ ├── initializePersistenceEngine()
57
+
58
+ ├── onInitialize()
59
+
60
+ ├── initializeDataEndpoints()
61
+ │ ├── Load schema model
62
+ │ ├── Create DAL for each entity
63
+ │ ├── Create Endpoints for each entity
64
+ │ └── Connect routes to Orator
65
+
66
+ ├── onAfterInitialize()
67
+
68
+ └── callback(error)
69
+ ```
70
+
71
+ ## Error Handling
72
+
73
+ If any hook or step passes an error to its callback, the initialization chain stops and the error is passed to the `initializeService` callback:
74
+
75
+ ```javascript
76
+ onInitialize(fCallback)
77
+ {
78
+ // Signal an error to stop initialization
79
+ return fCallback(new Error('Database migration failed'));
80
+ }
81
+ ```