meadow-integration 1.0.5 → 1.0.7
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/.dockerignore +11 -0
- package/Docker-Build.sh +2 -0
- package/Docker-Compose.sh +2 -0
- package/Docker-Push.sh +2 -0
- package/Docker-Tag.sh +2 -0
- package/Dockerfile +28 -0
- package/Dockerfile_LUXURYCode +23 -0
- package/README.md +139 -25
- package/docker-compose.yml +16 -0
- package/docs/README.md +65 -18
- package/docs/_cover.md +3 -2
- package/docs/_sidebar.md +52 -7
- package/docs/_topbar.md +2 -0
- package/docs/api/clone-rest-client.md +278 -0
- package/docs/api/connection-manager.md +179 -0
- package/docs/api/guid-map.md +234 -0
- package/docs/api/integration-adapter.md +283 -0
- package/docs/api/operation.md +241 -0
- package/docs/api/sync-entity-initial.md +227 -0
- package/docs/api/sync-entity-ongoing.md +244 -0
- package/docs/api/sync.md +213 -0
- package/docs/api/tabular-check.md +213 -0
- package/docs/api/tabular-transform.md +316 -0
- package/docs/architecture.md +423 -0
- package/docs/cli/comprehensionarray.md +111 -0
- package/docs/cli/comprehensionintersect.md +132 -0
- package/docs/cli/csvcheck.md +111 -0
- package/docs/cli/csvtransform.md +170 -0
- package/docs/cli/data-clone.md +277 -0
- package/docs/cli/jsonarraytransform.md +166 -0
- package/docs/cli/load-comprehension.md +129 -0
- package/docs/cli/objectarraytocsv.md +159 -0
- package/docs/cli/overview.md +96 -0
- package/docs/cli/serve.md +102 -0
- package/docs/cli/tsvtransform.md +144 -0
- package/docs/data-clone/configuration.md +357 -0
- package/docs/data-clone/connection-manager.md +206 -0
- package/docs/data-clone/docker.md +290 -0
- package/docs/data-clone/overview.md +173 -0
- package/docs/data-clone/sync-modes.md +186 -0
- package/docs/implementation-reference.md +311 -0
- package/docs/overview.md +156 -0
- package/docs/quickstart.md +233 -0
- package/docs/rest/comprehension-push.md +209 -0
- package/docs/rest/comprehension.md +506 -0
- package/docs/rest/csv.md +255 -0
- package/docs/rest/entity-generation.md +158 -0
- package/docs/rest/json-array.md +243 -0
- package/docs/rest/overview.md +120 -0
- package/docs/rest/status.md +63 -0
- package/docs/rest/tsv.md +241 -0
- package/docs/retold-catalog.json +93 -3
- package/docs/retold-keyword-index.json +23683 -1901
- package/package.json +6 -3
- package/scripts/run.sh +18 -0
- package/source/Meadow-Integration.js +15 -1
- package/source/cli/Default-Meadow-Integration-Configuration.json +37 -2
- package/source/cli/Meadow-Integration-CLI-Program.js +4 -1
- package/source/cli/commands/Meadow-Integration-Command-DataClone.js +284 -0
- package/source/services/clone/Meadow-Service-ConnectionManager.js +251 -0
- package/source/services/clone/Meadow-Service-Operation.js +196 -0
- package/source/services/clone/Meadow-Service-RestClient.js +364 -0
- package/source/services/clone/Meadow-Service-Sync-Entity-Initial.js +502 -0
- package/source/services/clone/Meadow-Service-Sync-Entity-Ongoing.js +592 -0
- package/source/services/clone/Meadow-Service-Sync.js +154 -0
- /package/docs/examples/bookstore/{mapping_books_Author.json → mapping_books_author.json} +0 -0
- /package/docs/examples/bookstore/{mapping_books_Book.json → mapping_books_book.json} +0 -0
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
# Service-TabularCheck (MeadowIntegrationTabularCheck)
|
|
2
|
+
|
|
3
|
+
Statistical analysis service for tabular data records. Collects column-level statistics including row counts, empty value counts, numeric detection, and first/last values for data quality inspection before transformation or integration.
|
|
4
|
+
|
|
5
|
+
**Source:** `source/services/tabular/Service-TabularCheck.js`
|
|
6
|
+
|
|
7
|
+
**Extends:** `fable-serviceproviderbase`
|
|
8
|
+
|
|
9
|
+
## Constructor
|
|
10
|
+
|
|
11
|
+
```js
|
|
12
|
+
const tabularCheck = fable.serviceManager.instantiateServiceProvider('TabularCheck', pOptions);
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
No custom options are required.
|
|
16
|
+
|
|
17
|
+
## Methods
|
|
18
|
+
|
|
19
|
+
### `newStatisticsObject(pTabularDatasetName)`
|
|
20
|
+
|
|
21
|
+
Creates a new, empty statistics container object for tracking dataset metrics.
|
|
22
|
+
|
|
23
|
+
| Parameter | Type | Description |
|
|
24
|
+
|-----------|------|-------------|
|
|
25
|
+
| `pTabularDatasetName` | `string` | A name identifying the dataset (e.g. `'airports.csv'`). |
|
|
26
|
+
|
|
27
|
+
**Returns:** `object` -- A statistics object with the following shape:
|
|
28
|
+
|
|
29
|
+
```js
|
|
30
|
+
{
|
|
31
|
+
DataSet: 'airports.csv', // Name of the dataset
|
|
32
|
+
FirstRow: null, // Reference to the first record processed
|
|
33
|
+
RowCount: 0, // Total number of rows processed
|
|
34
|
+
LastRow: null, // Reference to the most recent record processed
|
|
35
|
+
Headers: [], // Array of column name strings, in discovery order
|
|
36
|
+
ColumnCount: 0, // Total unique columns discovered
|
|
37
|
+
ColumnStatistics: {}, // Per-column statistics (see below)
|
|
38
|
+
Records: null // Array of all records (only if pStoreFullRecord is true)
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### `collectStatistics(pRecord, pStatisticsObject, pStoreFullRecord)`
|
|
43
|
+
|
|
44
|
+
Processes a single record and updates the statistics object with its data.
|
|
45
|
+
|
|
46
|
+
| Parameter | Type | Default | Description |
|
|
47
|
+
|-----------|------|---------|-------------|
|
|
48
|
+
| `pRecord` | `object` | -- | A single data record (key-value object). |
|
|
49
|
+
| `pStatisticsObject` | `object` | *(auto-created)* | The statistics object to update. If not a valid object, a new one is created automatically. |
|
|
50
|
+
| `pStoreFullRecord` | `boolean` | `false` | If `true`, pushes the full record into `Records` array for later inspection. |
|
|
51
|
+
|
|
52
|
+
**Returns:** `object` -- The updated statistics object.
|
|
53
|
+
|
|
54
|
+
**Per-column statistics shape** (each entry in `ColumnStatistics`):
|
|
55
|
+
|
|
56
|
+
```js
|
|
57
|
+
{
|
|
58
|
+
Count: 0, // Number of records that have this column
|
|
59
|
+
EmptyCount: 0, // Number of records where this column is null or empty string
|
|
60
|
+
NumericCount: 0, // Number of records where this column's value is numeric
|
|
61
|
+
FirstValue: null, // The first non-null value seen for this column
|
|
62
|
+
LastValue: null // The most recent value seen for this column
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**Behavior notes:**
|
|
67
|
+
- Each call increments `RowCount` by 1.
|
|
68
|
+
- The first record processed is stored as `FirstRow`; every record updates `LastRow`.
|
|
69
|
+
- New columns are added to `Headers` and `ColumnStatistics` as they are discovered.
|
|
70
|
+
- A value is considered numeric if `fable.Math.parsePrecise(value, NaN)` returns a valid number.
|
|
71
|
+
- A value is considered empty if it is strictly `null` or an empty string `''`.
|
|
72
|
+
- Callers are responsible for ensuring each record is only sent through once.
|
|
73
|
+
|
|
74
|
+
## Statistics Object Shape (Complete)
|
|
75
|
+
|
|
76
|
+
```js
|
|
77
|
+
{
|
|
78
|
+
DataSet: 'my-dataset',
|
|
79
|
+
FirstRow: { Name: 'Alice', Age: '30', City: 'Portland' },
|
|
80
|
+
RowCount: 1000,
|
|
81
|
+
LastRow: { Name: 'Zoe', Age: '25', City: 'Seattle' },
|
|
82
|
+
Headers: ['Name', 'Age', 'City'],
|
|
83
|
+
ColumnCount: 3,
|
|
84
|
+
ColumnStatistics:
|
|
85
|
+
{
|
|
86
|
+
Name:
|
|
87
|
+
{
|
|
88
|
+
Count: 1000,
|
|
89
|
+
EmptyCount: 5,
|
|
90
|
+
NumericCount: 0,
|
|
91
|
+
FirstValue: 'Alice',
|
|
92
|
+
LastValue: 'Zoe'
|
|
93
|
+
},
|
|
94
|
+
Age:
|
|
95
|
+
{
|
|
96
|
+
Count: 1000,
|
|
97
|
+
EmptyCount: 12,
|
|
98
|
+
NumericCount: 988,
|
|
99
|
+
FirstValue: '30',
|
|
100
|
+
LastValue: '25'
|
|
101
|
+
},
|
|
102
|
+
City:
|
|
103
|
+
{
|
|
104
|
+
Count: 1000,
|
|
105
|
+
EmptyCount: 50,
|
|
106
|
+
NumericCount: 0,
|
|
107
|
+
FirstValue: 'Portland',
|
|
108
|
+
LastValue: 'Seattle'
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
Records: null
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Usage Examples
|
|
116
|
+
|
|
117
|
+
### Basic Dataset Statistics
|
|
118
|
+
|
|
119
|
+
```js
|
|
120
|
+
const libFable = require('fable');
|
|
121
|
+
const libTabularCheck = require('meadow-integration/source/services/tabular/Service-TabularCheck');
|
|
122
|
+
|
|
123
|
+
const fable = new libFable({ Product: 'DataInspector' });
|
|
124
|
+
|
|
125
|
+
fable.serviceManager.addServiceType('TabularCheck', libTabularCheck);
|
|
126
|
+
const checker = fable.serviceManager.instantiateServiceProvider('TabularCheck');
|
|
127
|
+
|
|
128
|
+
// Create a statistics container
|
|
129
|
+
const stats = checker.newStatisticsObject('customers.csv');
|
|
130
|
+
|
|
131
|
+
// Process records one by one (e.g. from a CSV stream)
|
|
132
|
+
const records =
|
|
133
|
+
[
|
|
134
|
+
{ Name: 'Alice', Email: 'alice@example.com', Age: '30' },
|
|
135
|
+
{ Name: 'Bob', Email: '', Age: '25' },
|
|
136
|
+
{ Name: '', Email: 'charlie@example.com', Age: 'unknown' },
|
|
137
|
+
{ Name: 'Diana', Email: 'diana@example.com', Age: '35' }
|
|
138
|
+
];
|
|
139
|
+
|
|
140
|
+
for (const record of records)
|
|
141
|
+
{
|
|
142
|
+
checker.collectStatistics(record, stats);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
console.log(`Dataset: ${stats.DataSet}`);
|
|
146
|
+
console.log(`Rows: ${stats.RowCount}`); // 4
|
|
147
|
+
console.log(`Columns: ${stats.ColumnCount}`); // 3
|
|
148
|
+
console.log(`Headers: ${stats.Headers.join(', ')}`); // Name, Email, Age
|
|
149
|
+
|
|
150
|
+
console.log('Name empty count:', stats.ColumnStatistics.Name.EmptyCount); // 1
|
|
151
|
+
console.log('Email empty count:', stats.ColumnStatistics.Email.EmptyCount); // 1
|
|
152
|
+
console.log('Age numeric count:', stats.ColumnStatistics.Age.NumericCount); // 3 (30, 25, 35)
|
|
153
|
+
console.log('Age first value:', stats.ColumnStatistics.Age.FirstValue); // '30'
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Storing Full Records for Inspection
|
|
157
|
+
|
|
158
|
+
```js
|
|
159
|
+
const stats = checker.newStatisticsObject('products.csv');
|
|
160
|
+
stats.Records = []; // Must initialize the Records array before enabling storage
|
|
161
|
+
|
|
162
|
+
for (const record of productRecords)
|
|
163
|
+
{
|
|
164
|
+
checker.collectStatistics(record, stats, true);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
console.log(`Stored ${stats.Records.length} records for inspection.`);
|
|
168
|
+
console.log('First stored record:', stats.Records[0]);
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Auto-Created Statistics Object
|
|
172
|
+
|
|
173
|
+
```js
|
|
174
|
+
// If you pass a non-object, a statistics object is created automatically
|
|
175
|
+
const stats = checker.collectStatistics({ Name: 'Alice', Age: '30' }, null);
|
|
176
|
+
console.log(stats.DataSet); // 'Unknown-{uuid}'
|
|
177
|
+
console.log(stats.RowCount); // 1
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Analyzing Column Quality
|
|
181
|
+
|
|
182
|
+
```js
|
|
183
|
+
const stats = checker.newStatisticsObject('orders.csv');
|
|
184
|
+
|
|
185
|
+
for (const record of orderRecords)
|
|
186
|
+
{
|
|
187
|
+
checker.collectStatistics(record, stats);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Report columns with high empty rates
|
|
191
|
+
for (const header of stats.Headers)
|
|
192
|
+
{
|
|
193
|
+
const colStats = stats.ColumnStatistics[header];
|
|
194
|
+
const emptyRate = (colStats.EmptyCount / colStats.Count * 100).toFixed(1);
|
|
195
|
+
|
|
196
|
+
if (colStats.EmptyCount > 0)
|
|
197
|
+
{
|
|
198
|
+
console.log(`Column "${header}": ${emptyRate}% empty (${colStats.EmptyCount}/${colStats.Count})`);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Detect likely numeric columns
|
|
202
|
+
const numericRate = (colStats.NumericCount / colStats.Count * 100).toFixed(1);
|
|
203
|
+
if (colStats.NumericCount > colStats.Count * 0.9)
|
|
204
|
+
{
|
|
205
|
+
console.log(`Column "${header}": likely numeric (${numericRate}% numeric values)`);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## Related Services
|
|
211
|
+
|
|
212
|
+
- [Service-TabularTransform](./tabular-transform.md) -- Transforms tabular records using mapping configurations; often used after TabularCheck validates the data shape.
|
|
213
|
+
- [MeadowIntegrationAdapter](./integration-adapter.md) -- Integrates transformed records into a Meadow data store.
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
# Service-TabularTransform (MeadowIntegrationTabularTransform)
|
|
2
|
+
|
|
3
|
+
Transformation service for mapping tabular data records into Meadow entity comprehensions. Uses configurable column mapping templates to convert external data (CSV rows, API results, etc.) into entity records with auto-generated GUIDs, template-based field values, and optional solver-based transformations.
|
|
4
|
+
|
|
5
|
+
**Source:** `source/services/tabular/Service-TabularTransform.js`
|
|
6
|
+
|
|
7
|
+
**Extends:** `fable-serviceproviderbase`
|
|
8
|
+
|
|
9
|
+
## Constructor
|
|
10
|
+
|
|
11
|
+
```js
|
|
12
|
+
const transform = fable.serviceManager.instantiateServiceProvider('TabularTransform', pOptions);
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
No custom options are required.
|
|
16
|
+
|
|
17
|
+
## Mapping Configuration
|
|
18
|
+
|
|
19
|
+
A mapping configuration defines how incoming data fields map to entity record fields. It can be provided explicitly or generated automatically from the first record.
|
|
20
|
+
|
|
21
|
+
### Mapping Configuration Shape
|
|
22
|
+
|
|
23
|
+
```js
|
|
24
|
+
{
|
|
25
|
+
"Entity": "Airport",
|
|
26
|
+
"GUIDTemplate": "Airport-{~D:iata~}",
|
|
27
|
+
"GUIDName": "GUIDAirport", // Auto-derived as GUID{Entity} if omitted
|
|
28
|
+
"Mappings":
|
|
29
|
+
{
|
|
30
|
+
"Code": "{~D:iata~}",
|
|
31
|
+
"Name": "{~D:name~}",
|
|
32
|
+
"Description": "{~D:name~} airport in {~D:city~}",
|
|
33
|
+
"City": "{~D:city~}",
|
|
34
|
+
"State": "{~D:state~}",
|
|
35
|
+
"Country": "{~D:country~}",
|
|
36
|
+
"Latitude": "{~D:lat~}",
|
|
37
|
+
"Longitude": "{~D:long~}"
|
|
38
|
+
},
|
|
39
|
+
"Solvers": [], // Optional array of expression solver definitions
|
|
40
|
+
"MultipleGUIDUniqueness": false, // If true, supports multiple GUID uniqueness entries per record
|
|
41
|
+
"ManyfestAddresses": false // If true, uses Manyfest setValueAtAddress for nested paths
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Template expressions use Fable's template engine syntax (e.g., `{~D:fieldname~}` or `{~Data:Record.fieldname~}`).
|
|
46
|
+
|
|
47
|
+
## Mapping Outcome Object
|
|
48
|
+
|
|
49
|
+
The mapping outcome object tracks the state and results of a transformation operation.
|
|
50
|
+
|
|
51
|
+
### `newMappingOutcomeObject()`
|
|
52
|
+
|
|
53
|
+
Creates a new, empty mapping outcome object.
|
|
54
|
+
|
|
55
|
+
**Returns:**
|
|
56
|
+
|
|
57
|
+
```js
|
|
58
|
+
{
|
|
59
|
+
Comprehension: {}, // Generated records keyed by entity name, then by GUID
|
|
60
|
+
ExistingComprehension: false, // Optional existing comprehension for merging with previous data
|
|
61
|
+
|
|
62
|
+
ImplicitConfiguration: false, // Auto-generated configuration from first record
|
|
63
|
+
ExplicitConfiguration: false, // User-provided mapping configuration file
|
|
64
|
+
UserConfiguration: {}, // Runtime overrides (e.g. different entity name)
|
|
65
|
+
Configuration: false, // Final merged configuration (Implicit + Explicit + User)
|
|
66
|
+
|
|
67
|
+
ParsedRowCount: 0, // Number of rows processed
|
|
68
|
+
BadRecords: [] // Records that failed validation (no GUID, invalid data)
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Methods
|
|
73
|
+
|
|
74
|
+
### `newMappingOutcomeObject()`
|
|
75
|
+
|
|
76
|
+
Creates a fresh mapping outcome container for a new transformation session.
|
|
77
|
+
|
|
78
|
+
**Returns:** `object` -- An empty mapping outcome object (see shape above).
|
|
79
|
+
|
|
80
|
+
### `generateMappingConfigurationPrototype(pRepresentativeString, pRecord)`
|
|
81
|
+
|
|
82
|
+
Auto-generates a mapping configuration from a representative string (typically a filename) and a sample record.
|
|
83
|
+
|
|
84
|
+
| Parameter | Type | Description |
|
|
85
|
+
|-----------|------|-------------|
|
|
86
|
+
| `pRepresentativeString` | `string` | A name used to derive the entity name (e.g. `'my favorite cats.csv'` becomes `'MyFavoriteCats'`). |
|
|
87
|
+
| `pRecord` | `object` | A sample record whose keys become the mapping fields. |
|
|
88
|
+
|
|
89
|
+
**Returns:** `object` -- A mapping configuration with auto-generated `Entity`, `GUIDTemplate`, and `Mappings`.
|
|
90
|
+
|
|
91
|
+
**Example:**
|
|
92
|
+
|
|
93
|
+
```js
|
|
94
|
+
const config = transform.generateMappingConfigurationPrototype('airport data', { iata: 'PDX', name: 'Portland' });
|
|
95
|
+
// Returns:
|
|
96
|
+
// {
|
|
97
|
+
// Entity: 'AirportData',
|
|
98
|
+
// GUIDTemplate: 'GUID-AirportData-{~Data:Record.iata~}',
|
|
99
|
+
// Mappings: {
|
|
100
|
+
// iata: '{~Data:Record.iata~}',
|
|
101
|
+
// name: '{~Data:Record.name~}'
|
|
102
|
+
// }
|
|
103
|
+
// }
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### `createRecordFromMapping(pRecord, pMapping, pRecordPrototype)`
|
|
107
|
+
|
|
108
|
+
Creates a single entity record by applying a mapping configuration to an incoming data record.
|
|
109
|
+
|
|
110
|
+
| Parameter | Type | Description |
|
|
111
|
+
|-----------|------|-------------|
|
|
112
|
+
| `pRecord` | `object` | The incoming data record (used as template data context). |
|
|
113
|
+
| `pMapping` | `object` | The mapping configuration with `GUIDName`, `GUIDTemplate`, and `Mappings`. |
|
|
114
|
+
| `pRecordPrototype` | `object\|null` | Optional base object to merge onto (deep-cloned). Defaults to `{}`. |
|
|
115
|
+
|
|
116
|
+
**Returns:** `object` -- The generated entity record with GUID and mapped fields.
|
|
117
|
+
|
|
118
|
+
**Behavior:**
|
|
119
|
+
- Sets the GUID field using `fable.parseTemplate(GUIDTemplate, pRecord)`.
|
|
120
|
+
- For each key in `Mappings`, resolves the template expression against `pRecord`.
|
|
121
|
+
- If `pMapping.ManyfestAddresses` is `true`, uses `fable.manifest.setValueAtAddress()` for nested property paths.
|
|
122
|
+
|
|
123
|
+
### `addRecordToComprehension(pIncomingRecord, pMappingOutcome, pNewRecordPrototype, pGUIDUniquenessString)`
|
|
124
|
+
|
|
125
|
+
Creates a record from mapping and adds it to the comprehension within the mapping outcome, handling duplicates and merging with existing data.
|
|
126
|
+
|
|
127
|
+
| Parameter | Type | Description |
|
|
128
|
+
|-----------|------|-------------|
|
|
129
|
+
| `pIncomingRecord` | `object` | The raw incoming data record. |
|
|
130
|
+
| `pMappingOutcome` | `object` | The mapping outcome object containing the comprehension. |
|
|
131
|
+
| `pNewRecordPrototype` | `object` | Optional record prototype for base values. |
|
|
132
|
+
| `pGUIDUniquenessString` | `string` | Optional uniqueness string injected as `_GUIDUniqueness` into the record before mapping. |
|
|
133
|
+
|
|
134
|
+
**Duplicate handling:**
|
|
135
|
+
- If the generated GUID already exists in the current `Comprehension`, the new record is merged onto the existing one via `Object.assign`.
|
|
136
|
+
- If the GUID exists in `ExistingComprehension` (previous run), it is pulled in and merged.
|
|
137
|
+
- If the record has no valid GUID, it is added to `BadRecords`.
|
|
138
|
+
|
|
139
|
+
### `transformRecord(pIncomingRecord, pMappingOutcomeObject)`
|
|
140
|
+
|
|
141
|
+
The primary entry point for transforming a single incoming record. Handles initialization, solver execution, and comprehension insertion.
|
|
142
|
+
|
|
143
|
+
| Parameter | Type | Description |
|
|
144
|
+
|-----------|------|-------------|
|
|
145
|
+
| `pIncomingRecord` | `object` | The raw incoming data record. |
|
|
146
|
+
| `pMappingOutcomeObject` | `object` | The mapping outcome object (created via `newMappingOutcomeObject()`). |
|
|
147
|
+
|
|
148
|
+
**Behavior:**
|
|
149
|
+
1. Initializes the mapping outcome if not already done (merges Implicit, Explicit, and User configurations).
|
|
150
|
+
2. Increments `ParsedRowCount`.
|
|
151
|
+
3. Creates a solution context object with `IncomingRecord`, `MappingConfiguration`, `RowIndex`, `Fable`, and `AppData`.
|
|
152
|
+
4. If `Configuration.Solvers` is defined, executes each solver expression via `fable.ExpressionParser.solve()`. Solvers can modify `NewRecordPrototype` and `NewRecordsGUIDUniqueness`.
|
|
153
|
+
5. If `MultipleGUIDUniqueness` is enabled and uniqueness entries exist, creates one record per uniqueness entry.
|
|
154
|
+
6. Otherwise, creates a single record and adds it to the comprehension.
|
|
155
|
+
|
|
156
|
+
### Lifecycle Hooks
|
|
157
|
+
|
|
158
|
+
Two empty methods are available for subclass overrides:
|
|
159
|
+
|
|
160
|
+
- **`onBeforeInitializeMappingOutcomeObject(pMappingOutcomeObject)`** -- Called before configuration merging.
|
|
161
|
+
- **`onAfterInitializeMappingOutcomeObject(pMappingOutcomeObject)`** -- Called after configuration merging and GUID name setup.
|
|
162
|
+
|
|
163
|
+
## Usage Examples
|
|
164
|
+
|
|
165
|
+
### Basic CSV-to-Entity Transformation
|
|
166
|
+
|
|
167
|
+
```js
|
|
168
|
+
const libFable = require('fable');
|
|
169
|
+
const libTabularTransform = require('meadow-integration/source/services/tabular/Service-TabularTransform');
|
|
170
|
+
|
|
171
|
+
const fable = new libFable({ Product: 'DataImporter' });
|
|
172
|
+
|
|
173
|
+
fable.serviceManager.addServiceType('TabularTransform', libTabularTransform);
|
|
174
|
+
const transform = fable.serviceManager.instantiateServiceProvider('TabularTransform');
|
|
175
|
+
|
|
176
|
+
// Define the mapping configuration
|
|
177
|
+
const mappingConfig =
|
|
178
|
+
{
|
|
179
|
+
Entity: 'Airport',
|
|
180
|
+
GUIDTemplate: 'Airport-{~Data:Record.iata~}',
|
|
181
|
+
Mappings:
|
|
182
|
+
{
|
|
183
|
+
Code: '{~Data:Record.iata~}',
|
|
184
|
+
Name: '{~Data:Record.name~}',
|
|
185
|
+
City: '{~Data:Record.city~}',
|
|
186
|
+
State: '{~Data:Record.state~}',
|
|
187
|
+
Country: '{~Data:Record.country~}'
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
// Create a mapping outcome and set the explicit configuration
|
|
192
|
+
const outcome = transform.newMappingOutcomeObject();
|
|
193
|
+
outcome.ExplicitConfiguration = mappingConfig;
|
|
194
|
+
|
|
195
|
+
// Transform each CSV row
|
|
196
|
+
const csvRows =
|
|
197
|
+
[
|
|
198
|
+
{ iata: 'PDX', name: 'Portland International', city: 'Portland', state: 'OR', country: 'US' },
|
|
199
|
+
{ iata: 'SEA', name: 'Seattle-Tacoma International', city: 'Seattle', state: 'WA', country: 'US' },
|
|
200
|
+
{ iata: 'SFO', name: 'San Francisco International', city: 'San Francisco', state: 'CA', country: 'US' }
|
|
201
|
+
];
|
|
202
|
+
|
|
203
|
+
for (const row of csvRows)
|
|
204
|
+
{
|
|
205
|
+
transform.transformRecord(row, outcome);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
console.log(`Parsed ${outcome.ParsedRowCount} rows.`);
|
|
209
|
+
console.log(`Bad records: ${outcome.BadRecords.length}`);
|
|
210
|
+
console.log('Airport records:', Object.keys(outcome.Comprehension.Airport));
|
|
211
|
+
// ['Airport-PDX', 'Airport-SEA', 'Airport-SFO']
|
|
212
|
+
|
|
213
|
+
const pdxRecord = outcome.Comprehension.Airport['Airport-PDX'];
|
|
214
|
+
console.log(pdxRecord);
|
|
215
|
+
// {
|
|
216
|
+
// GUIDAirport: 'Airport-PDX',
|
|
217
|
+
// Code: 'PDX',
|
|
218
|
+
// Name: 'Portland International',
|
|
219
|
+
// City: 'Portland',
|
|
220
|
+
// State: 'OR',
|
|
221
|
+
// Country: 'US'
|
|
222
|
+
// }
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Auto-Generated Configuration from Data
|
|
226
|
+
|
|
227
|
+
```js
|
|
228
|
+
const transform = fable.serviceManager.instantiateServiceProvider('TabularTransform');
|
|
229
|
+
|
|
230
|
+
// Auto-generate a mapping from a sample record
|
|
231
|
+
const sampleRecord = { iata: 'PDX', name: 'Portland', city: 'Portland', state: 'OR' };
|
|
232
|
+
const autoConfig = transform.generateMappingConfigurationPrototype('airports', sampleRecord);
|
|
233
|
+
|
|
234
|
+
console.log(autoConfig.Entity); // 'Airports'
|
|
235
|
+
console.log(autoConfig.GUIDTemplate); // 'GUID-Airports-{~Data:Record.iata~}'
|
|
236
|
+
console.log(autoConfig.Mappings);
|
|
237
|
+
// {
|
|
238
|
+
// iata: '{~Data:Record.iata~}',
|
|
239
|
+
// name: '{~Data:Record.name~}',
|
|
240
|
+
// city: '{~Data:Record.city~}',
|
|
241
|
+
// state: '{~Data:Record.state~}'
|
|
242
|
+
// }
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### Creating a Single Record from Mapping
|
|
246
|
+
|
|
247
|
+
```js
|
|
248
|
+
const mapping =
|
|
249
|
+
{
|
|
250
|
+
GUIDName: 'GUIDProduct',
|
|
251
|
+
GUIDTemplate: 'PROD-{~Data:Record.sku~}',
|
|
252
|
+
Mappings:
|
|
253
|
+
{
|
|
254
|
+
Name: '{~Data:Record.name~}',
|
|
255
|
+
Price: '{~Data:Record.price~}'
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
const record = transform.createRecordFromMapping(
|
|
260
|
+
{ sku: 'ABC-123', name: 'Widget', price: '19.99' },
|
|
261
|
+
mapping);
|
|
262
|
+
|
|
263
|
+
console.log(record);
|
|
264
|
+
// {
|
|
265
|
+
// GUIDProduct: 'PROD-ABC-123',
|
|
266
|
+
// Name: 'Widget',
|
|
267
|
+
// Price: '19.99'
|
|
268
|
+
// }
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### Merging with Existing Comprehension
|
|
272
|
+
|
|
273
|
+
```js
|
|
274
|
+
const outcome = transform.newMappingOutcomeObject();
|
|
275
|
+
outcome.ExplicitConfiguration = mappingConfig;
|
|
276
|
+
|
|
277
|
+
// Provide existing data from a previous run
|
|
278
|
+
outcome.ExistingComprehension =
|
|
279
|
+
{
|
|
280
|
+
Airport:
|
|
281
|
+
{
|
|
282
|
+
'Airport-PDX': { GUIDAirport: 'Airport-PDX', Code: 'PDX', Name: 'Portland Intl', Rating: 5 }
|
|
283
|
+
}
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
// Transform a record that matches an existing GUID
|
|
287
|
+
transform.transformRecord(
|
|
288
|
+
{ iata: 'PDX', name: 'Portland International (Updated)', city: 'Portland', state: 'OR', country: 'US' },
|
|
289
|
+
outcome);
|
|
290
|
+
|
|
291
|
+
// The existing record is merged with new data
|
|
292
|
+
const merged = outcome.Comprehension.Airport['Airport-PDX'];
|
|
293
|
+
console.log(merged.Rating); // 5 (preserved from existing)
|
|
294
|
+
console.log(merged.Name); // 'Portland International (Updated)' (overwritten by new data)
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### Using User Configuration Overrides
|
|
298
|
+
|
|
299
|
+
```js
|
|
300
|
+
const outcome = transform.newMappingOutcomeObject();
|
|
301
|
+
outcome.ExplicitConfiguration = mappingConfig;
|
|
302
|
+
|
|
303
|
+
// Override the entity name at runtime
|
|
304
|
+
outcome.UserConfiguration = { Entity: 'InternationalAirport' };
|
|
305
|
+
|
|
306
|
+
transform.transformRecord(csvRows[0], outcome);
|
|
307
|
+
|
|
308
|
+
// Records are stored under the overridden entity name
|
|
309
|
+
console.log(Object.keys(outcome.Comprehension)); // ['InternationalAirport']
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
## Related Services
|
|
313
|
+
|
|
314
|
+
- [Service-TabularCheck](./tabular-check.md) -- Statistical analysis for tabular data; often used before transformation to validate the data shape.
|
|
315
|
+
- [MeadowIntegrationAdapter](./integration-adapter.md) -- Integrates the comprehension output from TabularTransform into a Meadow data store.
|
|
316
|
+
- [MeadowGUIDMap](./guid-map.md) -- Maintains GUID-to-ID mappings that may be needed during integration of transformed records.
|