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.
- package/.vscode/settings.json +33 -14
- package/CONTRIBUTING.md +50 -0
- package/README.md +200 -56
- package/docs/.nojekyll +0 -0
- package/docs/README.md +76 -0
- package/docs/_sidebar.md +13 -0
- package/docs/architecture.md +94 -0
- package/docs/behavior-injection.md +111 -0
- package/docs/configuration.md +93 -0
- package/docs/cover.md +11 -0
- package/docs/dal-access.md +87 -0
- package/docs/endpoints.md +121 -0
- package/docs/index.html +39 -0
- package/docs/initialization.md +77 -0
- package/docs/lifecycle-hooks.md +81 -0
- package/docs/schema-definition.md +128 -0
- package/docs/storage-providers.md +109 -0
- package/package.json +10 -6
- package/source/Retold-Data-Service.js +22 -13
- package/test/RetoldDataService_tests.js +1315 -18
|
@@ -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
|
+
```
|
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="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
|
+
```
|