fable 3.1.72 → 3.1.74
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/docs/README.md +30 -6
- package/docs/_brand.json +18 -0
- package/docs/_playground.json +10 -0
- package/docs/_sidebar.md +2 -0
- package/docs/_version.json +3 -3
- package/docs/architecture.md +201 -39
- package/docs/index.html +6 -7
- package/docs/pict-docuserve.min.js +91 -0
- package/docs/pict-docuserve.min.js.map +1 -0
- package/docs/playground.md +38 -0
- package/docs/retold-catalog.json +1 -1
- package/docs/retold-keyword-index.json +8721 -8105
- package/docs/services/README.md +26 -9
- package/docs/services/anticipate.md +104 -40
- package/docs/services/csv-parser.md +63 -35
- package/docs/services/data-format.md +154 -49
- package/docs/services/data-generation.md +77 -16
- package/docs/services/dates.md +103 -36
- package/docs/services/environment-data.md +13 -2
- package/docs/services/expression-parser.md +280 -68
- package/docs/services/file-persistence.md +142 -150
- package/docs/services/logging.md +93 -37
- package/docs/services/logic.md +70 -22
- package/docs/services/manifest.md +114 -26
- package/docs/services/math.md +168 -63
- package/docs/services/meta-template.md +312 -158
- package/docs/services/object-cache.md +94 -11
- package/docs/services/operation.md +68 -6
- package/docs/services/progress-time.md +74 -13
- package/docs/services/progress-tracker-set.md +101 -3
- package/docs/services/rest-client.md +136 -104
- package/docs/services/settings-manager.md +133 -40
- package/docs/services/template.md +71 -22
- package/docs/services/utility.md +121 -29
- package/docs/services/uuid.md +58 -10
- package/package.json +2 -2
- package/source/services/Fable-Service-MetaTemplate/MetaTemplate-StringParser.js +6 -0
- package/test/MetaTemplating_tests.js +77 -0
- package/.claude/settings.local.json +0 -8
- package/docs/css/docuserve.css +0 -327
package/docs/services/README.md
CHANGED
|
@@ -45,23 +45,35 @@ These services are created when first requested:
|
|
|
45
45
|
### Accessing Services
|
|
46
46
|
|
|
47
47
|
```javascript
|
|
48
|
+
const libFable = require('fable');
|
|
49
|
+
const fable = new libFable({ Product: 'ServicesDemo', ProductVersion: '1.0.0' });
|
|
50
|
+
|
|
48
51
|
// Auto-instantiated services are available directly
|
|
49
|
-
fable.Dates.dayJS().format('YYYY-MM-DD');
|
|
50
|
-
fable.Math.addPrecise('1', '2');
|
|
52
|
+
console.log('Today:', fable.Dates.dayJS().format('YYYY-MM-DD'));
|
|
53
|
+
console.log('1 + 2 =', fable.Math.addPrecise('1', '2'));
|
|
51
54
|
|
|
52
55
|
// On-demand services need to be instantiated first
|
|
53
56
|
const restClient = fable.instantiateServiceProvider('RestClient');
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
+
console.log('restClient instantiated:', typeof restClient);
|
|
58
|
+
|
|
59
|
+
// In Node.js you would then call:
|
|
60
|
+
// restClient.getJSON('https://api.example.com/data', (err, res, data) => console.log(data));
|
|
61
|
+
// (Network calls are skipped here so the playground demo stays self-contained.)
|
|
57
62
|
```
|
|
58
63
|
|
|
59
64
|
### Creating Multiple Instances
|
|
60
65
|
|
|
61
66
|
```javascript
|
|
67
|
+
const libFable = require('fable');
|
|
68
|
+
const fable = new libFable({ Product: 'ServicesDemo', ProductVersion: '1.0.0' });
|
|
69
|
+
|
|
62
70
|
// Create named instances for different purposes
|
|
63
|
-
const apiClient
|
|
71
|
+
const apiClient = fable.instantiateServiceProvider('RestClient', {}, 'api');
|
|
64
72
|
const authClient = fable.instantiateServiceProvider('RestClient', {}, 'auth');
|
|
73
|
+
console.log('apiClient typeof:', typeof apiClient);
|
|
74
|
+
console.log('authClient typeof:', typeof authClient);
|
|
75
|
+
console.log('Both instances live in fable.servicesMap.RestClient — keys:',
|
|
76
|
+
Object.keys(fable.servicesMap.RestClient));
|
|
65
77
|
```
|
|
66
78
|
|
|
67
79
|
### Service Options
|
|
@@ -69,8 +81,13 @@ const authClient = fable.instantiateServiceProvider('RestClient', {}, 'auth');
|
|
|
69
81
|
Most services accept an options object during instantiation:
|
|
70
82
|
|
|
71
83
|
```javascript
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
84
|
+
const libFable = require('fable');
|
|
85
|
+
const fable = new libFable({ Product: 'ServicesDemo', ProductVersion: '1.0.0' });
|
|
86
|
+
|
|
87
|
+
// Shape of the call — replace 'Template' with whichever service you want.
|
|
88
|
+
const service = fable.instantiateServiceProvider('Template', {
|
|
89
|
+
// option1: 'value1',
|
|
90
|
+
// option2: 'value2'
|
|
75
91
|
}, 'optional-hash');
|
|
92
|
+
console.log('Service instantiated via the generic pattern:', typeof service);
|
|
76
93
|
```
|
|
@@ -5,11 +5,16 @@ The Anticipate service provides asynchronous operation sequencing, allowing you
|
|
|
5
5
|
## Access
|
|
6
6
|
|
|
7
7
|
```javascript
|
|
8
|
+
const libFable = require('fable');
|
|
9
|
+
const fable = new libFable({ Product: 'AnticipateDemo', ProductVersion: '1.0.0' });
|
|
10
|
+
|
|
8
11
|
// On-demand service - instantiate when needed
|
|
9
|
-
const
|
|
12
|
+
const tmpAnticipateService = fable.instantiateServiceProvider('Anticipate');
|
|
13
|
+
console.log('Service instance:', typeof tmpAnticipateService);
|
|
10
14
|
|
|
11
15
|
// Or use the factory method (creates unregistered instance)
|
|
12
|
-
const
|
|
16
|
+
const tmpAnticipateFactory = fable.newAnticipate();
|
|
17
|
+
console.log('Factory instance:', typeof tmpAnticipateFactory);
|
|
13
18
|
```
|
|
14
19
|
|
|
15
20
|
## Basic Usage
|
|
@@ -17,6 +22,9 @@ const tmpAnticipate = fable.newAnticipate();
|
|
|
17
22
|
### Sequential Operations
|
|
18
23
|
|
|
19
24
|
```javascript
|
|
25
|
+
const libFable = require('fable');
|
|
26
|
+
const fable = new libFable({ Product: 'AnticipateDemo', ProductVersion: '1.0.0' });
|
|
27
|
+
|
|
20
28
|
const tmpAnticipate = fable.newAnticipate();
|
|
21
29
|
|
|
22
30
|
tmpAnticipate.anticipate(function (fCallback)
|
|
@@ -60,6 +68,14 @@ tmpAnticipate.wait(function (pError)
|
|
|
60
68
|
Since Anticipate callbacks receive only `fCallback`, use closure scope or external variables to share data between steps:
|
|
61
69
|
|
|
62
70
|
```javascript
|
|
71
|
+
const libFable = require('fable');
|
|
72
|
+
const fable = new libFable({ Product: 'AnticipateDemo', ProductVersion: '1.0.0' });
|
|
73
|
+
|
|
74
|
+
// Stubbed async helpers for the playground demo
|
|
75
|
+
function fetchUser(pId, fCallback) { setTimeout(() => fCallback({ id: pId, name: 'User#' + pId }), 5); }
|
|
76
|
+
function loadPreferences(pId, fCallback) { setTimeout(() => fCallback({ theme: 'dark' }), 5); }
|
|
77
|
+
function render(pUser, pPrefs) { console.log('rendered:', pUser, pPrefs); }
|
|
78
|
+
|
|
63
79
|
const tmpAnticipate = fable.newAnticipate();
|
|
64
80
|
let tmpUser = null;
|
|
65
81
|
let tmpPreferences = null;
|
|
@@ -91,6 +107,10 @@ tmpAnticipate.wait(function (pError)
|
|
|
91
107
|
{
|
|
92
108
|
console.error('Workflow failed:', pError);
|
|
93
109
|
}
|
|
110
|
+
else
|
|
111
|
+
{
|
|
112
|
+
console.log('Workflow done.');
|
|
113
|
+
}
|
|
94
114
|
});
|
|
95
115
|
```
|
|
96
116
|
|
|
@@ -101,11 +121,17 @@ tmpAnticipate.wait(function (pError)
|
|
|
101
121
|
Add an asynchronous operation to the queue. Operations run sequentially by default (one at a time).
|
|
102
122
|
|
|
103
123
|
```javascript
|
|
124
|
+
const libFable = require('fable');
|
|
125
|
+
const fable = new libFable({ Product: 'AnticipateDemo', ProductVersion: '1.0.0' });
|
|
126
|
+
const tmpAnticipate = fable.newAnticipate();
|
|
127
|
+
|
|
104
128
|
tmpAnticipate.anticipate(function (fCallback)
|
|
105
129
|
{
|
|
106
130
|
// Do async work
|
|
131
|
+
console.log('async work running');
|
|
107
132
|
fCallback(); // Call when done, or fCallback(pError) to signal failure
|
|
108
133
|
});
|
|
134
|
+
tmpAnticipate.wait(function (pError) { console.log('wait done, pError:', pError); });
|
|
109
135
|
```
|
|
110
136
|
|
|
111
137
|
The step function receives one parameter:
|
|
@@ -116,12 +142,21 @@ The step function receives one parameter:
|
|
|
116
142
|
Register a callback to run when all queued operations complete (or when an error occurs):
|
|
117
143
|
|
|
118
144
|
```javascript
|
|
145
|
+
const libFable = require('fable');
|
|
146
|
+
const fable = new libFable({ Product: 'AnticipateDemo', ProductVersion: '1.0.0' });
|
|
147
|
+
const tmpAnticipate = fable.newAnticipate();
|
|
148
|
+
|
|
149
|
+
tmpAnticipate.anticipate(function (fCallback) { console.log('step done'); fCallback(); });
|
|
119
150
|
tmpAnticipate.wait(function (pError)
|
|
120
151
|
{
|
|
121
152
|
if (pError)
|
|
122
153
|
{
|
|
123
154
|
console.error('Error:', pError);
|
|
124
155
|
}
|
|
156
|
+
else
|
|
157
|
+
{
|
|
158
|
+
console.log('All operations completed');
|
|
159
|
+
}
|
|
125
160
|
});
|
|
126
161
|
```
|
|
127
162
|
|
|
@@ -130,8 +165,12 @@ tmpAnticipate.wait(function (pError)
|
|
|
130
165
|
Control concurrency. Default is `1` (sequential). Set higher for parallel execution:
|
|
131
166
|
|
|
132
167
|
```javascript
|
|
168
|
+
const libFable = require('fable');
|
|
169
|
+
const fable = new libFable({ Product: 'AnticipateDemo', ProductVersion: '1.0.0' });
|
|
170
|
+
|
|
133
171
|
const tmpAnticipate = fable.newAnticipate();
|
|
134
172
|
tmpAnticipate.maxOperations = 5; // Run up to 5 operations concurrently
|
|
173
|
+
console.log('maxOperations:', tmpAnticipate.maxOperations);
|
|
135
174
|
```
|
|
136
175
|
|
|
137
176
|
## Error Handling
|
|
@@ -141,6 +180,9 @@ tmpAnticipate.maxOperations = 5; // Run up to 5 operations concurrently
|
|
|
141
180
|
When an operation passes an error to its callback, remaining queued operations are skipped and the `wait` callback fires immediately with the error:
|
|
142
181
|
|
|
143
182
|
```javascript
|
|
183
|
+
const libFable = require('fable');
|
|
184
|
+
const fable = new libFable({ Product: 'AnticipateDemo', ProductVersion: '1.0.0' });
|
|
185
|
+
|
|
144
186
|
const tmpAnticipate = fable.newAnticipate();
|
|
145
187
|
let tmpPostErrorRan = false;
|
|
146
188
|
|
|
@@ -162,8 +204,8 @@ tmpAnticipate.anticipate(function (fCallback)
|
|
|
162
204
|
});
|
|
163
205
|
tmpAnticipate.wait(function (pError)
|
|
164
206
|
{
|
|
165
|
-
console.log('
|
|
166
|
-
console.log('Step 3 ran:',
|
|
207
|
+
console.log('Caught error:', pError && pError.message); // Something went wrong
|
|
208
|
+
console.log('Step 3 ran:', tmpPostErrorRan); // false
|
|
167
209
|
});
|
|
168
210
|
```
|
|
169
211
|
|
|
@@ -172,6 +214,11 @@ tmpAnticipate.wait(function (pError)
|
|
|
172
214
|
To handle errors without bailing out, catch them inside the step and continue:
|
|
173
215
|
|
|
174
216
|
```javascript
|
|
217
|
+
const libFable = require('fable');
|
|
218
|
+
const fable = new libFable({ Product: 'AnticipateDemo', ProductVersion: '1.0.0' });
|
|
219
|
+
|
|
220
|
+
function riskyOperation() { throw new Error('demo failure'); }
|
|
221
|
+
|
|
175
222
|
const tmpAnticipate = fable.newAnticipate();
|
|
176
223
|
let tmpRecoveredError = null;
|
|
177
224
|
|
|
@@ -192,13 +239,13 @@ tmpAnticipate.anticipate(function (fCallback)
|
|
|
192
239
|
{
|
|
193
240
|
if (tmpRecoveredError)
|
|
194
241
|
{
|
|
195
|
-
console.log('Recovered from:', tmpRecoveredError);
|
|
242
|
+
console.log('Recovered from:', tmpRecoveredError.message);
|
|
196
243
|
}
|
|
197
244
|
fCallback();
|
|
198
245
|
});
|
|
199
246
|
tmpAnticipate.wait(function (pError)
|
|
200
247
|
{
|
|
201
|
-
console.log('Pipeline completed');
|
|
248
|
+
console.log('Pipeline completed, pError:', pError);
|
|
202
249
|
});
|
|
203
250
|
```
|
|
204
251
|
|
|
@@ -207,6 +254,12 @@ tmpAnticipate.wait(function (pError)
|
|
|
207
254
|
### Database Migrations
|
|
208
255
|
|
|
209
256
|
```javascript
|
|
257
|
+
const libFable = require('fable');
|
|
258
|
+
const fable = new libFable({ Product: 'AnticipateDemo', ProductVersion: '1.0.0' });
|
|
259
|
+
|
|
260
|
+
// Stub DB driver for the playground demo
|
|
261
|
+
const db = { query: (sql, cb) => { console.log('SQL:', sql); cb(null); } };
|
|
262
|
+
|
|
210
263
|
const tmpMigrate = fable.newAnticipate();
|
|
211
264
|
|
|
212
265
|
tmpMigrate.anticipate(function (fCallback)
|
|
@@ -231,6 +284,17 @@ tmpMigrate.wait(function (pError)
|
|
|
231
284
|
### API Request Chains
|
|
232
285
|
|
|
233
286
|
```javascript
|
|
287
|
+
const libFable = require('fable');
|
|
288
|
+
const fable = new libFable({ Product: 'AnticipateDemo', ProductVersion: '1.0.0' });
|
|
289
|
+
|
|
290
|
+
// Stub API + credentials + process for the playground demo
|
|
291
|
+
const credentials = { user: 'demo', pass: 'demo' };
|
|
292
|
+
const api = {
|
|
293
|
+
login: (creds, cb) => setTimeout(() => cb('tok-' + Math.floor(Math.random() * 1000)), 5),
|
|
294
|
+
getData: (tok, cb) => setTimeout(() => cb({ rows: 3, tok }), 5)
|
|
295
|
+
};
|
|
296
|
+
function processData(pData) { console.log('processed data:', pData); }
|
|
297
|
+
|
|
234
298
|
const tmpWorkflow = fable.newAnticipate();
|
|
235
299
|
let tmpToken = null;
|
|
236
300
|
let tmpData = null;
|
|
@@ -253,33 +317,33 @@ tmpWorkflow.anticipate(function (fCallback)
|
|
|
253
317
|
});
|
|
254
318
|
tmpWorkflow.anticipate(function (fCallback)
|
|
255
319
|
{
|
|
256
|
-
|
|
320
|
+
processData(tmpData);
|
|
257
321
|
fCallback();
|
|
258
322
|
});
|
|
259
323
|
tmpWorkflow.wait(function (pError)
|
|
260
324
|
{
|
|
261
325
|
if (pError) console.error('Workflow failed:', pError);
|
|
326
|
+
else console.log('Workflow OK; token:', tmpToken);
|
|
262
327
|
});
|
|
263
328
|
```
|
|
264
329
|
|
|
265
330
|
### Parallel Operations
|
|
266
331
|
|
|
267
332
|
```javascript
|
|
333
|
+
const libFable = require('fable');
|
|
334
|
+
const fable = new libFable({ Product: 'AnticipateDemo', ProductVersion: '1.0.0' });
|
|
335
|
+
|
|
336
|
+
// Stub fetchers for the playground demo
|
|
337
|
+
function fetchFromServiceA(cb) { setTimeout(() => { console.log('A done'); cb(); }, 5); }
|
|
338
|
+
function fetchFromServiceB(cb) { setTimeout(() => { console.log('B done'); cb(); }, 5); }
|
|
339
|
+
function fetchFromServiceC(cb) { setTimeout(() => { console.log('C done'); cb(); }, 5); }
|
|
340
|
+
|
|
268
341
|
const tmpParallel = fable.newAnticipate();
|
|
269
342
|
tmpParallel.maxOperations = 3; // Run up to 3 at a time
|
|
270
343
|
|
|
271
|
-
tmpParallel.anticipate(function (fCallback)
|
|
272
|
-
{
|
|
273
|
-
|
|
274
|
-
});
|
|
275
|
-
tmpParallel.anticipate(function (fCallback)
|
|
276
|
-
{
|
|
277
|
-
fetchFromServiceB(function () { fCallback(); });
|
|
278
|
-
});
|
|
279
|
-
tmpParallel.anticipate(function (fCallback)
|
|
280
|
-
{
|
|
281
|
-
fetchFromServiceC(function () { fCallback(); });
|
|
282
|
-
});
|
|
344
|
+
tmpParallel.anticipate(function (fCallback) { fetchFromServiceA(function () { fCallback(); }); });
|
|
345
|
+
tmpParallel.anticipate(function (fCallback) { fetchFromServiceB(function () { fCallback(); }); });
|
|
346
|
+
tmpParallel.anticipate(function (fCallback) { fetchFromServiceC(function () { fCallback(); }); });
|
|
283
347
|
tmpParallel.wait(function (pError)
|
|
284
348
|
{
|
|
285
349
|
console.log('All fetches complete');
|
|
@@ -289,24 +353,21 @@ tmpParallel.wait(function (pError)
|
|
|
289
353
|
### Build Pipeline
|
|
290
354
|
|
|
291
355
|
```javascript
|
|
356
|
+
const libFable = require('fable');
|
|
357
|
+
const fable = new libFable({ Product: 'AnticipateDemo', ProductVersion: '1.0.0' });
|
|
358
|
+
|
|
359
|
+
// Stub build steps for the playground demo
|
|
360
|
+
function cleanBuildDir(cb) { console.log('clean'); cb(); }
|
|
361
|
+
function compile(cb) { console.log('compile'); cb(); }
|
|
362
|
+
function bundle(cb) { console.log('bundle'); cb(); }
|
|
363
|
+
function minify(cb) { console.log('minify'); cb(); }
|
|
364
|
+
|
|
292
365
|
const tmpBuild = fable.newAnticipate();
|
|
293
366
|
|
|
294
|
-
tmpBuild.anticipate(function (fCallback)
|
|
295
|
-
{
|
|
296
|
-
|
|
297
|
-
});
|
|
298
|
-
tmpBuild.anticipate(function (fCallback)
|
|
299
|
-
{
|
|
300
|
-
compile(fCallback);
|
|
301
|
-
});
|
|
302
|
-
tmpBuild.anticipate(function (fCallback)
|
|
303
|
-
{
|
|
304
|
-
bundle(fCallback);
|
|
305
|
-
});
|
|
306
|
-
tmpBuild.anticipate(function (fCallback)
|
|
307
|
-
{
|
|
308
|
-
minify(fCallback);
|
|
309
|
-
});
|
|
367
|
+
tmpBuild.anticipate(function (fCallback) { cleanBuildDir(fCallback); });
|
|
368
|
+
tmpBuild.anticipate(function (fCallback) { compile(fCallback); });
|
|
369
|
+
tmpBuild.anticipate(function (fCallback) { bundle(fCallback); });
|
|
370
|
+
tmpBuild.anticipate(function (fCallback) { minify(fCallback); });
|
|
310
371
|
tmpBuild.wait(function (pError)
|
|
311
372
|
{
|
|
312
373
|
if (pError) throw pError;
|
|
@@ -319,13 +380,16 @@ tmpBuild.wait(function (pError)
|
|
|
319
380
|
Create multiple independent workflows:
|
|
320
381
|
|
|
321
382
|
```javascript
|
|
322
|
-
const
|
|
383
|
+
const libFable = require('fable');
|
|
384
|
+
const fable = new libFable({ Product: 'AnticipateDemo', ProductVersion: '1.0.0' });
|
|
385
|
+
|
|
386
|
+
const tmpUserWorkflow = fable.newAnticipate();
|
|
323
387
|
const tmpOrderWorkflow = fable.newAnticipate();
|
|
324
388
|
|
|
325
389
|
// These run independently
|
|
326
|
-
tmpUserWorkflow.anticipate(function (fCallback) {
|
|
327
|
-
tmpUserWorkflow.wait(function (pError) {
|
|
390
|
+
tmpUserWorkflow.anticipate(function (fCallback) { console.log('user step'); fCallback(); });
|
|
391
|
+
tmpUserWorkflow.wait(function (pError) { console.log('user workflow done'); });
|
|
328
392
|
|
|
329
|
-
tmpOrderWorkflow.anticipate(function (fCallback) {
|
|
330
|
-
tmpOrderWorkflow.wait(function (pError) {
|
|
393
|
+
tmpOrderWorkflow.anticipate(function (fCallback) { console.log('order step'); fCallback(); });
|
|
394
|
+
tmpOrderWorkflow.wait(function (pError) { console.log('order workflow done'); });
|
|
331
395
|
```
|
|
@@ -5,8 +5,12 @@ The CSVParser service provides line-by-line CSV parsing with support for multi-l
|
|
|
5
5
|
## Access
|
|
6
6
|
|
|
7
7
|
```javascript
|
|
8
|
+
const libFable = require('fable');
|
|
9
|
+
const fable = new libFable({ Product: 'CSVDemo', ProductVersion: '1.0.0' });
|
|
10
|
+
|
|
8
11
|
// On-demand service - instantiate when needed
|
|
9
12
|
const csvParser = fable.instantiateServiceProvider('CSVParser', {}, 'CSV Parser-123');
|
|
13
|
+
console.log('csvParser:', typeof csvParser);
|
|
10
14
|
```
|
|
11
15
|
|
|
12
16
|
## Basic Usage
|
|
@@ -16,28 +20,32 @@ const csvParser = fable.instantiateServiceProvider('CSVParser', {}, 'CSV Parser-
|
|
|
16
20
|
The primary method is `parseCSVLine()`, which processes one line at a time and returns a parsed record (or `false` if the line is part of a multi-line quoted field or is the header row):
|
|
17
21
|
|
|
18
22
|
```javascript
|
|
19
|
-
const
|
|
20
|
-
const
|
|
21
|
-
|
|
23
|
+
const libFable = require('fable');
|
|
24
|
+
const fable = new libFable({ Product: 'CSVDemo', ProductVersion: '1.0.0' });
|
|
22
25
|
const csvParser = fable.instantiateServiceProvider('CSVParser');
|
|
23
|
-
const records = [];
|
|
24
26
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
rl.on('
|
|
31
|
-
|
|
32
|
-
|
|
27
|
+
// In Node.js the typical streaming pattern is:
|
|
28
|
+
// const libFS = require('fs');
|
|
29
|
+
// const libReadline = require('readline');
|
|
30
|
+
// const rl = libReadline.createInterface({ input: libFS.createReadStream('data.csv'), crlfDelay: Infinity });
|
|
31
|
+
// rl.on('line', line => { const rec = csvParser.parseCSVLine(line); if (rec) records.push(rec); });
|
|
32
|
+
// rl.on('close', () => console.log(`Parsed ${records.length} records`));
|
|
33
|
+
//
|
|
34
|
+
// The browser playground has no fs/readline, so the snippet below parses an
|
|
35
|
+
// in-memory CSV string line-by-line using the exact same parseCSVLine() loop:
|
|
36
|
+
|
|
37
|
+
const csvSource = "name,age,city\nJohn,30,New York\nJane,25,Boston";
|
|
38
|
+
const records = [];
|
|
39
|
+
for (const line of csvSource.split('\n'))
|
|
40
|
+
{
|
|
41
|
+
const record = csvParser.parseCSVLine(line);
|
|
42
|
+
if (record)
|
|
43
|
+
{
|
|
33
44
|
records.push(record);
|
|
34
45
|
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
console.log(`Parsed ${records.length} records`);
|
|
39
|
-
// records[0] is an object like { name: 'John', age: '30', city: 'New York' }
|
|
40
|
-
});
|
|
46
|
+
}
|
|
47
|
+
console.log(`Parsed ${records.length} records`);
|
|
48
|
+
console.log('records[0]:', records[0]);
|
|
41
49
|
```
|
|
42
50
|
|
|
43
51
|
### Using FilePersistence Wrapper
|
|
@@ -45,17 +53,13 @@ rl.on('close', () => {
|
|
|
45
53
|
The FilePersistence service provides a convenience method for CSV reading:
|
|
46
54
|
|
|
47
55
|
```javascript
|
|
48
|
-
fable.
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
(
|
|
52
|
-
|
|
53
|
-
console.log(record.name, record.age);
|
|
54
|
-
|
|
55
|
-
() => {
|
|
56
|
-
// Called when parsing is complete
|
|
57
|
-
console.log('Done!');
|
|
58
|
-
});
|
|
56
|
+
// Node.js reference — fable.FilePersistence.readFileCSV uses fs, which the
|
|
57
|
+
// browser playground doesn't expose. The shape of the call:
|
|
58
|
+
console.info("In Node.js:");
|
|
59
|
+
console.info(" fable.instantiateServiceProvider('FilePersistence');");
|
|
60
|
+
console.info(" fable.FilePersistence.readFileCSV('data.csv', {},");
|
|
61
|
+
console.info(" (record) => console.log(record.name, record.age),");
|
|
62
|
+
console.info(" () => console.log('Done!'));");
|
|
59
63
|
```
|
|
60
64
|
|
|
61
65
|
## How Parsing Works
|
|
@@ -77,6 +81,9 @@ The CSVParser is stateful and processes lines sequentially:
|
|
|
77
81
|
Set these properties on the parser instance after creation:
|
|
78
82
|
|
|
79
83
|
```javascript
|
|
84
|
+
const libFable = require('fable');
|
|
85
|
+
const fable = new libFable({ Product: 'CSVDemo', ProductVersion: '1.0.0' });
|
|
86
|
+
|
|
80
87
|
const csvParser = fable.instantiateServiceProvider('CSVParser');
|
|
81
88
|
|
|
82
89
|
csvParser.Delimiter = ';'; // Default: ','
|
|
@@ -86,6 +93,11 @@ csvParser.HeaderLineIndex = 0; // Default: 0 - which line is the header
|
|
|
86
93
|
csvParser.EmitJSON = true; // Default: true - emit objects vs arrays
|
|
87
94
|
csvParser.EmitHeader = false; // Default: false - return header row or skip it
|
|
88
95
|
csvParser.EscapedQuoteString = '"'; // Default: '"' - replacement for escaped quotes
|
|
96
|
+
|
|
97
|
+
console.log('Delimiter:', csvParser.Delimiter);
|
|
98
|
+
console.log('QuoteCharacter:', csvParser.QuoteCharacter);
|
|
99
|
+
console.log('HasHeader:', csvParser.HasHeader);
|
|
100
|
+
console.log('EmitJSON:', csvParser.EmitJSON);
|
|
89
101
|
```
|
|
90
102
|
|
|
91
103
|
## Methods
|
|
@@ -99,7 +111,12 @@ Parse a single line of CSV. Returns a record object, an array, or `false`.
|
|
|
99
111
|
Manually set column headers (an array of strings):
|
|
100
112
|
|
|
101
113
|
```javascript
|
|
114
|
+
const libFable = require('fable');
|
|
115
|
+
const fable = new libFable({ Product: 'CSVDemo', ProductVersion: '1.0.0' });
|
|
116
|
+
const csvParser = fable.instantiateServiceProvider('CSVParser');
|
|
117
|
+
|
|
102
118
|
csvParser.setHeader(['name', 'age', 'city']);
|
|
119
|
+
console.log('Header set; field names:', csvParser.HeaderFieldNames);
|
|
103
120
|
```
|
|
104
121
|
|
|
105
122
|
### `marshalRowToJSON(rowArray)`
|
|
@@ -107,8 +124,12 @@ csvParser.setHeader(['name', 'age', 'city']);
|
|
|
107
124
|
Convert a row array into a JSON object using the current headers:
|
|
108
125
|
|
|
109
126
|
```javascript
|
|
127
|
+
const libFable = require('fable');
|
|
128
|
+
const fable = new libFable({ Product: 'CSVDemo', ProductVersion: '1.0.0' });
|
|
129
|
+
const csvParser = fable.instantiateServiceProvider('CSVParser');
|
|
130
|
+
|
|
110
131
|
csvParser.setHeader(['name', 'age']);
|
|
111
|
-
csvParser.marshalRowToJSON(['John', '30']);
|
|
132
|
+
console.log(csvParser.marshalRowToJSON(['John', '30']));
|
|
112
133
|
// Returns { name: 'John', age: '30' }
|
|
113
134
|
```
|
|
114
135
|
|
|
@@ -123,11 +144,18 @@ Emit the currently accumulated row. If `formatAsJSON` is true (default), returns
|
|
|
123
144
|
## Parser State Properties
|
|
124
145
|
|
|
125
146
|
```javascript
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
csvParser
|
|
129
|
-
|
|
130
|
-
|
|
147
|
+
const libFable = require('fable');
|
|
148
|
+
const fable = new libFable({ Product: 'CSVDemo', ProductVersion: '1.0.0' });
|
|
149
|
+
const csvParser = fable.instantiateServiceProvider('CSVParser');
|
|
150
|
+
|
|
151
|
+
// Parse a small CSV inline so the state properties have something to show:
|
|
152
|
+
for (const line of "name,age\nJohn,30".split('\n')) csvParser.parseCSVLine(line);
|
|
153
|
+
|
|
154
|
+
console.log('LinesParsed:', csvParser.LinesParsed);
|
|
155
|
+
console.log('RowsEmitted:', csvParser.RowsEmitted);
|
|
156
|
+
console.log('InQuote:', csvParser.InQuote);
|
|
157
|
+
console.log('Header:', csvParser.Header);
|
|
158
|
+
console.log('HeaderFieldNames:', csvParser.HeaderFieldNames);
|
|
131
159
|
```
|
|
132
160
|
|
|
133
161
|
## Handling Special Cases
|