retold 4.0.1 → 4.0.2
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/.claude/settings.local.json +27 -1
- package/docs/README.md +7 -6
- package/docs/_sidebar.md +34 -21
- package/docs/_topbar.md +2 -2
- package/docs/architecture/module-architecture.md +234 -0
- package/docs/{modules.md → architecture/modules.md} +25 -22
- package/docs/cover.md +2 -2
- package/docs/css/docuserve.css +6 -6
- package/docs/examples/examples.md +71 -0
- package/docs/examples/todolist/todo-list-cli-client.md +178 -0
- package/docs/examples/todolist/todo-list-console-client.md +152 -0
- package/docs/examples/todolist/todo-list-model.md +114 -0
- package/docs/examples/todolist/todo-list-server.md +128 -0
- package/docs/examples/todolist/todo-list-web-client.md +177 -0
- package/docs/examples/todolist/todo-list.md +162 -0
- package/docs/getting-started.md +8 -7
- package/docs/index.html +4 -4
- package/docs/{meadow.md → modules/meadow.md} +4 -6
- package/docs/{orator.md → modules/orator.md} +1 -0
- package/docs/{pict.md → modules/pict.md} +30 -8
- package/docs/{utility.md → modules/utility.md} +0 -9
- package/docs/retold-catalog.json +896 -2317
- package/docs/retold-keyword-index.json +162327 -120227
- package/examples/todo-list/Dockerfile +45 -0
- package/examples/todo-list/README.md +394 -0
- package/examples/todo-list/cli-client/package-lock.json +418 -0
- package/examples/todo-list/cli-client/package.json +19 -0
- package/examples/todo-list/cli-client/source/TodoCLI-CLIProgram.js +30 -0
- package/examples/todo-list/cli-client/source/TodoCLI-Run.js +3 -0
- package/examples/todo-list/cli-client/source/commands/add/TodoCLI-Command-Add.js +74 -0
- package/examples/todo-list/cli-client/source/commands/complete/TodoCLI-Command-Complete.js +84 -0
- package/examples/todo-list/cli-client/source/commands/list/TodoCLI-Command-List.js +110 -0
- package/examples/todo-list/cli-client/source/commands/remove/TodoCLI-Command-Remove.js +49 -0
- package/examples/todo-list/cli-client/source/services/TodoCLI-Service-API.js +92 -0
- package/examples/todo-list/console-client/console-client.cjs +913 -0
- package/examples/todo-list/console-client/package-lock.json +426 -0
- package/examples/todo-list/console-client/package.json +19 -0
- package/examples/todo-list/console-client/views/PictView-TUI-Header.cjs +43 -0
- package/examples/todo-list/console-client/views/PictView-TUI-Layout.cjs +58 -0
- package/examples/todo-list/console-client/views/PictView-TUI-StatusBar.cjs +41 -0
- package/examples/todo-list/console-client/views/PictView-TUI-TaskList.cjs +104 -0
- package/examples/todo-list/docker-motd.sh +36 -0
- package/examples/todo-list/docker-run.sh +2 -0
- package/examples/todo-list/docker-shell.sh +2 -0
- package/examples/todo-list/model/MeadowSchema-Task.json +152 -0
- package/examples/todo-list/model/Task-Compiled.json +25 -0
- package/examples/todo-list/model/Task.mddl +15 -0
- package/examples/todo-list/model/data/seeded_todo_events.csv +1001 -0
- package/examples/todo-list/server/database-initialization-service.cjs +273 -0
- package/examples/todo-list/server/package-lock.json +6113 -0
- package/examples/todo-list/server/package.json +19 -0
- package/examples/todo-list/server/server.cjs +138 -0
- package/examples/todo-list/web-client/css/todolist-theme.css +235 -0
- package/examples/todo-list/web-client/generate-build-config.cjs +18 -0
- package/examples/todo-list/web-client/html/index.html +18 -0
- package/examples/todo-list/web-client/package-lock.json +12030 -0
- package/examples/todo-list/web-client/package.json +43 -0
- package/examples/todo-list/web-client/source/TodoList-Application-Config.json +12 -0
- package/examples/todo-list/web-client/source/TodoList-Application.cjs +383 -0
- package/examples/todo-list/web-client/source/providers/Provider-TaskData.cjs +243 -0
- package/examples/todo-list/web-client/source/providers/Router-Config.json +32 -0
- package/examples/todo-list/web-client/source/views/View-Layout.cjs +75 -0
- package/examples/todo-list/web-client/source/views/View-TaskForm.cjs +87 -0
- package/examples/todo-list/web-client/source/views/View-TaskList.cjs +127 -0
- package/examples/todo-list/web-client/source/views/calendar/View-MonthView.cjs +293 -0
- package/examples/todo-list/web-client/source/views/calendar/View-WeekView.cjs +149 -0
- package/examples/todo-list/web-client/source/views/calendar/View-YearView.cjs +226 -0
- package/modules/Include-Retold-Module-List.sh +2 -2
- package/package.json +5 -5
- package/docs/js/pict.min.js +0 -12
- package/docs/js/pict.min.js.map +0 -1
- package/docs/pict-docuserve.min.js +0 -58
- package/docs/pict-docuserve.min.js.map +0 -1
- /package/docs/{architecture.md → architecture/architecture.md} +0 -0
- /package/docs/{fable.md → modules/fable.md} +0 -0
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Database Initialization Service
|
|
3
|
+
*
|
|
4
|
+
* A Fable service provider that manages SQLite database setup:
|
|
5
|
+
* creating tables from compiled Stricture DDL and seeding initial
|
|
6
|
+
* data from a CSV file through the Meadow DAL.
|
|
7
|
+
*
|
|
8
|
+
* Table creation is delegated to the meadow-connection-sqlite provider.
|
|
9
|
+
* Seeding is done by reading a CSV file and creating records through
|
|
10
|
+
* the Meadow DAL, so all standard Meadow behaviors (GUID generation,
|
|
11
|
+
* audit stamps, default values) are applied automatically.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const libFS = require('fs');
|
|
15
|
+
const libPath = require('path');
|
|
16
|
+
|
|
17
|
+
const libFableServiceProviderBase = require('fable-serviceproviderbase');
|
|
18
|
+
|
|
19
|
+
class DatabaseInitializationService extends libFableServiceProviderBase
|
|
20
|
+
{
|
|
21
|
+
constructor(pFable, pOptions, pServiceHash)
|
|
22
|
+
{
|
|
23
|
+
super(pFable, pOptions, pServiceHash);
|
|
24
|
+
|
|
25
|
+
this.serviceType = 'DatabaseInitializationService';
|
|
26
|
+
|
|
27
|
+
// The data directory where the SQLite file lives
|
|
28
|
+
this.dataDirectory = this.options.DataDirectory || libPath.resolve(__dirname, 'data');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Ensure the data directory exists for the SQLite file.
|
|
33
|
+
*/
|
|
34
|
+
ensureDataDirectory()
|
|
35
|
+
{
|
|
36
|
+
if (!libFS.existsSync(this.dataDirectory))
|
|
37
|
+
{
|
|
38
|
+
libFS.mkdirSync(this.dataDirectory, { recursive: true });
|
|
39
|
+
this.fable.log.info(`Created data directory: ${this.dataDirectory}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Connect the SQLite provider and open the database.
|
|
45
|
+
*
|
|
46
|
+
* @param {function} fCallback - Callback(pError)
|
|
47
|
+
*/
|
|
48
|
+
connectDatabase(fCallback)
|
|
49
|
+
{
|
|
50
|
+
this.ensureDataDirectory();
|
|
51
|
+
|
|
52
|
+
this.fable.MeadowSQLiteProvider.connectAsync(
|
|
53
|
+
(pError) =>
|
|
54
|
+
{
|
|
55
|
+
if (pError)
|
|
56
|
+
{
|
|
57
|
+
this.fable.log.error('SQLite connection error: ' + pError.message);
|
|
58
|
+
return fCallback(pError);
|
|
59
|
+
}
|
|
60
|
+
this.fable.log.info('SQLite database connected.');
|
|
61
|
+
return fCallback();
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Create tables from a compiled Stricture model using the SQLite provider.
|
|
67
|
+
*
|
|
68
|
+
* The provider's createTables() method generates proper SQLite DDL from the
|
|
69
|
+
* Stricture compiled table format ({ TableName, Columns: [{ Column, DataType, Size }] }).
|
|
70
|
+
*
|
|
71
|
+
* @param {object} pCompiledModel - A compiled Stricture model (with .Tables array)
|
|
72
|
+
* @param {function} fCallback - Callback(pError)
|
|
73
|
+
*/
|
|
74
|
+
createTablesFromModel(pCompiledModel, fCallback)
|
|
75
|
+
{
|
|
76
|
+
this.fable.MeadowSQLiteProvider.createTables(pCompiledModel, fCallback);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Parse a CSV string into an array of objects using the header row as keys.
|
|
81
|
+
*
|
|
82
|
+
* Handles quoted fields (including fields with commas and escaped quotes).
|
|
83
|
+
*
|
|
84
|
+
* @param {string} pCSVContent - Raw CSV text
|
|
85
|
+
* @returns {Array} Array of row objects keyed by header column names
|
|
86
|
+
*/
|
|
87
|
+
parseCSV(pCSVContent)
|
|
88
|
+
{
|
|
89
|
+
let tmpRows = [];
|
|
90
|
+
let tmpLines = pCSVContent.split('\n');
|
|
91
|
+
|
|
92
|
+
if (tmpLines.length < 2)
|
|
93
|
+
{
|
|
94
|
+
return tmpRows;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
let tmpHeaders = this._parseCSVLine(tmpLines[0]);
|
|
98
|
+
|
|
99
|
+
for (let i = 1; i < tmpLines.length; i++)
|
|
100
|
+
{
|
|
101
|
+
let tmpLine = tmpLines[i].trim();
|
|
102
|
+
if (!tmpLine)
|
|
103
|
+
{
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
let tmpValues = this._parseCSVLine(tmpLine);
|
|
108
|
+
let tmpRow = {};
|
|
109
|
+
|
|
110
|
+
for (let j = 0; j < tmpHeaders.length; j++)
|
|
111
|
+
{
|
|
112
|
+
tmpRow[tmpHeaders[j]] = (j < tmpValues.length) ? tmpValues[j] : '';
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
tmpRows.push(tmpRow);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return tmpRows;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Parse a single CSV line into an array of field values.
|
|
123
|
+
*
|
|
124
|
+
* @param {string} pLine - A single CSV line
|
|
125
|
+
* @returns {Array} Array of string values
|
|
126
|
+
*/
|
|
127
|
+
_parseCSVLine(pLine)
|
|
128
|
+
{
|
|
129
|
+
let tmpFields = [];
|
|
130
|
+
let tmpCurrent = '';
|
|
131
|
+
let tmpInQuotes = false;
|
|
132
|
+
|
|
133
|
+
for (let i = 0; i < pLine.length; i++)
|
|
134
|
+
{
|
|
135
|
+
let tmpChar = pLine[i];
|
|
136
|
+
|
|
137
|
+
if (tmpInQuotes)
|
|
138
|
+
{
|
|
139
|
+
if (tmpChar === '"')
|
|
140
|
+
{
|
|
141
|
+
// Check for escaped quote ("")
|
|
142
|
+
if (i + 1 < pLine.length && pLine[i + 1] === '"')
|
|
143
|
+
{
|
|
144
|
+
tmpCurrent += '"';
|
|
145
|
+
i++;
|
|
146
|
+
}
|
|
147
|
+
else
|
|
148
|
+
{
|
|
149
|
+
tmpInQuotes = false;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
else
|
|
153
|
+
{
|
|
154
|
+
tmpCurrent += tmpChar;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
else
|
|
158
|
+
{
|
|
159
|
+
if (tmpChar === '"')
|
|
160
|
+
{
|
|
161
|
+
tmpInQuotes = true;
|
|
162
|
+
}
|
|
163
|
+
else if (tmpChar === ',')
|
|
164
|
+
{
|
|
165
|
+
tmpFields.push(tmpCurrent.trim());
|
|
166
|
+
tmpCurrent = '';
|
|
167
|
+
}
|
|
168
|
+
else
|
|
169
|
+
{
|
|
170
|
+
tmpCurrent += tmpChar;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
tmpFields.push(tmpCurrent.trim());
|
|
176
|
+
return tmpFields;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Seed a table from a CSV file if the table is currently empty.
|
|
181
|
+
*
|
|
182
|
+
* Reads the CSV, parses it, and creates records through the Meadow DAL
|
|
183
|
+
* so that GUID generation, audit stamps and default values are applied.
|
|
184
|
+
*
|
|
185
|
+
* @param {object} pMeadowDAL - A Meadow DAL instance for the target table
|
|
186
|
+
* @param {string} pCSVFilePath - Absolute path to the CSV seed file
|
|
187
|
+
* @param {function} fCallback - Callback(pError)
|
|
188
|
+
*/
|
|
189
|
+
seedFromCSV(pMeadowDAL, pCSVFilePath, fCallback)
|
|
190
|
+
{
|
|
191
|
+
let tmpTableName = pMeadowDAL.scope;
|
|
192
|
+
|
|
193
|
+
// Check if the table already has data
|
|
194
|
+
let tmpDB = this.fable.MeadowSQLiteProvider.db;
|
|
195
|
+
let tmpCount = tmpDB.prepare(`SELECT COUNT(*) AS Count FROM ${tmpTableName}`).get();
|
|
196
|
+
|
|
197
|
+
if (tmpCount.Count > 0)
|
|
198
|
+
{
|
|
199
|
+
this.fable.log.info(`Table [${tmpTableName}] already has ${tmpCount.Count} rows; skipping CSV seed.`);
|
|
200
|
+
return fCallback();
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Read and parse the CSV
|
|
204
|
+
if (!libFS.existsSync(pCSVFilePath))
|
|
205
|
+
{
|
|
206
|
+
this.fable.log.warn(`Seed file not found: ${pCSVFilePath}; skipping seed.`);
|
|
207
|
+
return fCallback();
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
let tmpCSVContent = libFS.readFileSync(pCSVFilePath, 'utf8');
|
|
211
|
+
let tmpRows = this.parseCSV(tmpCSVContent);
|
|
212
|
+
|
|
213
|
+
if (tmpRows.length === 0)
|
|
214
|
+
{
|
|
215
|
+
this.fable.log.info(`Seed file ${pCSVFilePath} is empty; skipping seed.`);
|
|
216
|
+
return fCallback();
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
this.fable.log.info(`Seeding [${tmpTableName}] with ${tmpRows.length} rows from CSV...`);
|
|
220
|
+
|
|
221
|
+
// Use Fable's Anticipate service to queue each doCreate serially.
|
|
222
|
+
// Anticipate handles synchronous callback chains (like better-sqlite3)
|
|
223
|
+
// gracefully, with built-in call depth protection.
|
|
224
|
+
let tmpAnticipate = this.fable.newAnticipate();
|
|
225
|
+
|
|
226
|
+
for (let i = 0; i < tmpRows.length; i++)
|
|
227
|
+
{
|
|
228
|
+
tmpAnticipate.anticipate(
|
|
229
|
+
(fNext) =>
|
|
230
|
+
{
|
|
231
|
+
let tmpRow = tmpRows[i];
|
|
232
|
+
|
|
233
|
+
// Convert LengthInHours to a number if present
|
|
234
|
+
if (tmpRow.hasOwnProperty('LengthInHours') && tmpRow.LengthInHours !== '')
|
|
235
|
+
{
|
|
236
|
+
tmpRow.LengthInHours = parseFloat(tmpRow.LengthInHours);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
let tmpQuery = pMeadowDAL.query.addRecord(tmpRow);
|
|
240
|
+
pMeadowDAL.doCreate(tmpQuery,
|
|
241
|
+
(pError, pQuery, pQueryRead, pRecord) =>
|
|
242
|
+
{
|
|
243
|
+
if (pError)
|
|
244
|
+
{
|
|
245
|
+
this.fable.log.error(`Error seeding row ${i + 1}: ${pError}`);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Log progress every 100 rows
|
|
249
|
+
if ((i + 1) % 100 === 0)
|
|
250
|
+
{
|
|
251
|
+
this.fable.log.info(` ... seeded ${i + 1} of ${tmpRows.length} rows`);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return fNext();
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
tmpAnticipate.wait(
|
|
260
|
+
(pError) =>
|
|
261
|
+
{
|
|
262
|
+
if (pError)
|
|
263
|
+
{
|
|
264
|
+
this.fable.log.error(`Error during CSV seed: ${pError}`);
|
|
265
|
+
return fCallback(pError);
|
|
266
|
+
}
|
|
267
|
+
this.fable.log.info(`Seeded ${tmpRows.length} rows into [${tmpTableName}].`);
|
|
268
|
+
return fCallback();
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
module.exports = DatabaseInitializationService;
|