meadow-integration 1.0.19 → 1.0.21

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.
Files changed (35) hide show
  1. package/example-applications/mapping-demo/.quackage.json +10 -0
  2. package/example-applications/mapping-demo/README.md +99 -0
  3. package/example-applications/mapping-demo/data/books-sample.csv +21 -0
  4. package/example-applications/mapping-demo/generate-build-config.js +44 -0
  5. package/example-applications/mapping-demo/mappings/books-to-book.json +14 -0
  6. package/example-applications/mapping-demo/package.json +14 -0
  7. package/example-applications/mapping-demo/server.js +814 -0
  8. package/example-applications/mapping-demo/source/MappingDemoApp.js +52 -0
  9. package/example-applications/mapping-demo/source/views/MappingDemoEditorView.js +186 -0
  10. package/example-applications/mapping-demo/web/index.html +892 -0
  11. package/example-applications/mapping-demo/web/mapping-demo-editor.js +3195 -0
  12. package/example-applications/mapping-demo/web/mapping-demo-editor.js.map +1 -0
  13. package/example-applications/mapping-demo/web/mapping-demo-editor.min.js +2 -0
  14. package/example-applications/mapping-demo/web/mapping-demo-editor.min.js.map +1 -0
  15. package/example-applications/mapping-demo/web/pict.min.js +12 -0
  16. package/package.json +11 -5
  17. package/source/Meadow-Integration-Browser.js +31 -0
  18. package/source/Meadow-Integration.js +30 -1
  19. package/source/services/certainty/Service-CertaintyAccumulator.js +402 -0
  20. package/source/services/clone/Meadow-Service-Sync-Entity-Initial.js +16 -3
  21. package/source/services/clone/Meadow-Service-Sync-Entity-Ongoing.js +15 -2
  22. package/source/services/clone/Meadow-Service-Sync.js +21 -0
  23. package/source/services/parser/Service-FileParser-CSV.js +263 -0
  24. package/source/services/parser/Service-FileParser-FixedWidth.js +158 -0
  25. package/source/services/parser/Service-FileParser-JSON.js +255 -0
  26. package/source/services/parser/Service-FileParser-XLSX.js +194 -0
  27. package/source/services/parser/Service-FileParser-XML.js +190 -0
  28. package/source/services/parser/Service-FileParser.js +142 -0
  29. package/source/views/MappingEditor-SchemaUtils.js +71 -0
  30. package/source/views/PictView-MeadowMappingEditor.js +1299 -0
  31. package/source/views/flow-cards/FlowCard-MappingSource.js +50 -0
  32. package/source/views/flow-cards/FlowCard-MappingTarget.js +49 -0
  33. package/source/views/flow-cards/FlowCard-SolverExpression.js +78 -0
  34. package/source/views/flow-cards/FlowCard-TemplateExpression.js +77 -0
  35. package/test/Meadow-Integration-CloneDeleteSync_test.js +809 -0
@@ -0,0 +1,194 @@
1
+ 'use strict';
2
+
3
+ const libFableServiceProviderBase = require('fable-serviceproviderbase');
4
+ const libFS = require('fs');
5
+
6
+ const defaultXLSXParserOptions = (
7
+ {
8
+ sheetName: '',
9
+ sheetIndex: 0,
10
+ headerRow: 1,
11
+ dataStartRow: 2,
12
+ maxFileSizeMB: 50
13
+ });
14
+
15
+ class MeadowIntegrationFileParserXLSX extends libFableServiceProviderBase
16
+ {
17
+ constructor(pFable, pOptions, pServiceHash)
18
+ {
19
+ let tmpOptions = Object.assign({}, defaultXLSXParserOptions, pOptions);
20
+ super(pFable, tmpOptions, pServiceHash);
21
+
22
+ this.serviceType = 'MeadowIntegrationFileParserXLSX';
23
+ }
24
+
25
+ /**
26
+ * Parse an XLSX file into an array of records.
27
+ * Entire file is read into memory. Enforces maxFileSizeMB guard.
28
+ *
29
+ * @param {string} pFilePath - Absolute path to the XLSX file
30
+ * @param {object} pOptions - Parser options
31
+ * @param {function} pChunkCallback - Called with (pError, pRecords) once with all records
32
+ * @param {function} pCompletionCallback - Called with (pError, pTotalCount) when done
33
+ */
34
+ parseFile(pFilePath, pOptions, pChunkCallback, pCompletionCallback)
35
+ {
36
+ let tmpOptions = Object.assign({}, this.options, pOptions);
37
+ let tmpMaxFileSizeMB = parseFloat(tmpOptions.maxFileSizeMB) || 50;
38
+ let tmpMaxBytes = tmpMaxFileSizeMB * 1024 * 1024;
39
+
40
+ let tmpStat;
41
+ try
42
+ {
43
+ tmpStat = libFS.statSync(pFilePath);
44
+ }
45
+ catch (pError)
46
+ {
47
+ return pCompletionCallback(new Error(`XLSX file stat error: ${pError.message}`));
48
+ }
49
+
50
+ if (tmpStat.size > tmpMaxBytes)
51
+ {
52
+ return pCompletionCallback(new Error(`XLSX file size ${(tmpStat.size / 1024 / 1024).toFixed(1)}MB exceeds maxFileSizeMB limit of ${tmpMaxFileSizeMB}MB`));
53
+ }
54
+
55
+ let tmpBuffer;
56
+ try
57
+ {
58
+ tmpBuffer = libFS.readFileSync(pFilePath);
59
+ }
60
+ catch (pError)
61
+ {
62
+ return pCompletionCallback(new Error(`XLSX file read error: ${pError.message}`));
63
+ }
64
+
65
+ this._parseBuffer(tmpBuffer, tmpOptions,
66
+ (pError, pRecords) =>
67
+ {
68
+ if (pError)
69
+ {
70
+ return pCompletionCallback(pError);
71
+ }
72
+ pChunkCallback(null, pRecords);
73
+ return pCompletionCallback(null, pRecords.length);
74
+ });
75
+ }
76
+
77
+ /**
78
+ * Parse XLSX content (Buffer) into a full array of records.
79
+ * Content must be a Buffer containing xlsx file bytes.
80
+ *
81
+ * @param {Buffer|string} pContent - XLSX file as Buffer (or base64 string)
82
+ * @param {object} pOptions - Parser options
83
+ * @param {function} fCallback - Called with (pError, pRecords)
84
+ */
85
+ parseContent(pContent, pOptions, fCallback)
86
+ {
87
+ let tmpOptions = Object.assign({}, this.options, pOptions);
88
+ let tmpBuffer = Buffer.isBuffer(pContent) ? pContent : Buffer.from(pContent, 'base64');
89
+ return this._parseBuffer(tmpBuffer, tmpOptions, fCallback);
90
+ }
91
+
92
+ /**
93
+ * Internal: parse an xlsx Buffer into records using the xlsx library.
94
+ *
95
+ * @param {Buffer} pBuffer - XLSX bytes
96
+ * @param {object} pOptions - Merged options
97
+ * @param {function} fCallback - Called with (pError, pRecords)
98
+ */
99
+ _parseBuffer(pBuffer, pOptions, fCallback)
100
+ {
101
+ let tmpXLSX;
102
+ try
103
+ {
104
+ tmpXLSX = require('xlsx');
105
+ }
106
+ catch (pError)
107
+ {
108
+ return fCallback(new Error(`xlsx library not available: ${pError.message}`));
109
+ }
110
+
111
+ let tmpWorkbook;
112
+ try
113
+ {
114
+ tmpWorkbook = tmpXLSX.read(pBuffer, { type: 'buffer' });
115
+ }
116
+ catch (pError)
117
+ {
118
+ return fCallback(new Error(`XLSX parse error: ${pError.message}`));
119
+ }
120
+
121
+ // Determine sheet to use
122
+ let tmpSheetName;
123
+ if (pOptions.sheetName && typeof pOptions.sheetName === 'string' && pOptions.sheetName.length > 0)
124
+ {
125
+ tmpSheetName = pOptions.sheetName;
126
+ }
127
+ else
128
+ {
129
+ let tmpSheetIndex = parseInt(pOptions.sheetIndex, 10) || 0;
130
+ tmpSheetName = tmpWorkbook.SheetNames[tmpSheetIndex];
131
+ }
132
+
133
+ if (!tmpSheetName || !tmpWorkbook.Sheets[tmpSheetName])
134
+ {
135
+ return fCallback(new Error(`XLSX sheet '${tmpSheetName}' not found in workbook`));
136
+ }
137
+
138
+ let tmpSheet = tmpWorkbook.Sheets[tmpSheetName];
139
+ let tmpHeaderRow = parseInt(pOptions.headerRow, 10) || 1;
140
+ let tmpDataStartRow = parseInt(pOptions.dataStartRow, 10) || 2;
141
+
142
+ // When headerRow and dataStartRow are at their defaults (1 and 2),
143
+ // use xlsx's built-in sheet_to_json which handles this automatically
144
+ if (tmpHeaderRow === 1 && tmpDataStartRow === 2)
145
+ {
146
+ try
147
+ {
148
+ let tmpRecords = tmpXLSX.utils.sheet_to_json(tmpSheet);
149
+ return fCallback(null, tmpRecords);
150
+ }
151
+ catch (pError)
152
+ {
153
+ return fCallback(new Error(`XLSX sheet_to_json error: ${pError.message}`));
154
+ }
155
+ }
156
+
157
+ // Custom header/data row offsets: read as raw array first
158
+ try
159
+ {
160
+ let tmpRawRows = tmpXLSX.utils.sheet_to_json(tmpSheet, { header: 1 });
161
+
162
+ // Header row is 1-based; convert to 0-based index
163
+ let tmpHeaderIdx = tmpHeaderRow - 1;
164
+ let tmpDataIdx = tmpDataStartRow - 1;
165
+
166
+ if (tmpHeaderIdx >= tmpRawRows.length)
167
+ {
168
+ return fCallback(new Error(`XLSX headerRow ${tmpHeaderRow} is beyond sheet row count`));
169
+ }
170
+
171
+ let tmpHeaders = tmpRawRows[tmpHeaderIdx];
172
+ let tmpRecords = [];
173
+
174
+ for (let i = tmpDataIdx; i < tmpRawRows.length; i++)
175
+ {
176
+ let tmpRow = tmpRawRows[i];
177
+ let tmpRecord = {};
178
+ for (let j = 0; j < tmpHeaders.length; j++)
179
+ {
180
+ tmpRecord[tmpHeaders[j]] = (tmpRow && tmpRow[j] !== undefined) ? tmpRow[j] : '';
181
+ }
182
+ tmpRecords.push(tmpRecord);
183
+ }
184
+
185
+ return fCallback(null, tmpRecords);
186
+ }
187
+ catch (pError)
188
+ {
189
+ return fCallback(new Error(`XLSX custom row parse error: ${pError.message}`));
190
+ }
191
+ }
192
+ }
193
+
194
+ module.exports = MeadowIntegrationFileParserXLSX;
@@ -0,0 +1,190 @@
1
+ 'use strict';
2
+
3
+ const libFableServiceProviderBase = require('fable-serviceproviderbase');
4
+ const libFS = require('fs');
5
+
6
+ const defaultXMLParserOptions = (
7
+ {
8
+ recordPath: '',
9
+ attributePrefix: '@_',
10
+ ignoreAttributes: false
11
+ });
12
+
13
+ class MeadowIntegrationFileParserXML extends libFableServiceProviderBase
14
+ {
15
+ constructor(pFable, pOptions, pServiceHash)
16
+ {
17
+ let tmpOptions = Object.assign({}, defaultXMLParserOptions, pOptions);
18
+ super(pFable, tmpOptions, pServiceHash);
19
+
20
+ this.serviceType = 'MeadowIntegrationFileParserXML';
21
+ }
22
+
23
+ /**
24
+ * Walk an XML-parsed object looking for the first array of object records.
25
+ * Recurses one level at a time: checks direct children first, then recurses.
26
+ *
27
+ * @param {object} pObject - Parsed XML object node
28
+ * @returns {Array|null} First array of objects found, or null
29
+ */
30
+ _extractXMLRecords(pObject)
31
+ {
32
+ if (!pObject || typeof pObject !== 'object')
33
+ {
34
+ return null;
35
+ }
36
+
37
+ let tmpKeys = Object.keys(pObject);
38
+
39
+ // First pass: look for array-valued keys whose elements are objects
40
+ for (let i = 0; i < tmpKeys.length; i++)
41
+ {
42
+ let tmpValue = pObject[tmpKeys[i]];
43
+ if (Array.isArray(tmpValue) && tmpValue.length > 0 && typeof tmpValue[0] === 'object')
44
+ {
45
+ return tmpValue;
46
+ }
47
+ }
48
+
49
+ // Second pass: recurse into object-valued keys
50
+ for (let i = 0; i < tmpKeys.length; i++)
51
+ {
52
+ let tmpValue = pObject[tmpKeys[i]];
53
+ if (typeof tmpValue === 'object' && !Array.isArray(tmpValue))
54
+ {
55
+ let tmpResult = this._extractXMLRecords(tmpValue);
56
+ if (tmpResult)
57
+ {
58
+ return tmpResult;
59
+ }
60
+ }
61
+ }
62
+
63
+ return null;
64
+ }
65
+
66
+ /**
67
+ * Navigate a parsed XML object using a dot-separated recordPath.
68
+ *
69
+ * @param {object} pParsed - Parsed XML object
70
+ * @param {string} pRecordPath - Dot-separated path to the records array
71
+ * @returns {Array|null} Records array or null
72
+ */
73
+ _resolveRecordPath(pParsed, pRecordPath)
74
+ {
75
+ let tmpParts = pRecordPath.split('.');
76
+ let tmpCurrent = pParsed;
77
+
78
+ for (let i = 0; i < tmpParts.length; i++)
79
+ {
80
+ if (!tmpCurrent || typeof tmpCurrent !== 'object' || !(tmpParts[i] in tmpCurrent))
81
+ {
82
+ return null;
83
+ }
84
+ tmpCurrent = tmpCurrent[tmpParts[i]];
85
+ }
86
+
87
+ return Array.isArray(tmpCurrent) ? tmpCurrent : [tmpCurrent];
88
+ }
89
+
90
+ /**
91
+ * Parse an XML file into an array of records.
92
+ * Reads the entire file into memory.
93
+ *
94
+ * @param {string} pFilePath - Absolute path to the XML file
95
+ * @param {object} pOptions - Parser options
96
+ * @param {function} pChunkCallback - Called with (pError, pRecords) once with all records
97
+ * @param {function} pCompletionCallback - Called with (pError, pTotalCount) when done
98
+ */
99
+ parseFile(pFilePath, pOptions, pChunkCallback, pCompletionCallback)
100
+ {
101
+ let tmpOptions = Object.assign({}, this.options, pOptions);
102
+
103
+ let tmpContent;
104
+ try
105
+ {
106
+ tmpContent = libFS.readFileSync(pFilePath, 'utf8');
107
+ }
108
+ catch (pError)
109
+ {
110
+ return pCompletionCallback(new Error(`XML file read error: ${pError.message}`));
111
+ }
112
+
113
+ this.parseContent(tmpContent, tmpOptions,
114
+ (pError, pRecords) =>
115
+ {
116
+ if (pError)
117
+ {
118
+ return pCompletionCallback(pError);
119
+ }
120
+ pChunkCallback(null, pRecords);
121
+ return pCompletionCallback(null, pRecords.length);
122
+ });
123
+ }
124
+
125
+ /**
126
+ * Parse XML content string into a full array of records.
127
+ *
128
+ * @param {string} pContent - Raw XML text
129
+ * @param {object} pOptions - Parser options
130
+ * @param {function} fCallback - Called with (pError, pRecords)
131
+ */
132
+ parseContent(pContent, pOptions, fCallback)
133
+ {
134
+ let tmpOptions = Object.assign({}, this.options, pOptions);
135
+ let tmpRecordPath = tmpOptions.recordPath || '';
136
+ let tmpAttributePrefix = tmpOptions.attributePrefix || '@_';
137
+ let tmpIgnoreAttributes = tmpOptions.ignoreAttributes === true;
138
+
139
+ let tmpXMLParser;
140
+ try
141
+ {
142
+ let libFastXMLParser = require('fast-xml-parser');
143
+ tmpXMLParser = new libFastXMLParser.XMLParser(
144
+ {
145
+ ignoreAttributes: tmpIgnoreAttributes,
146
+ attributeNamePrefix: tmpAttributePrefix
147
+ });
148
+ }
149
+ catch (pError)
150
+ {
151
+ return fCallback(new Error(`fast-xml-parser library not available: ${pError.message}`));
152
+ }
153
+
154
+ let tmpParsed;
155
+ try
156
+ {
157
+ tmpParsed = tmpXMLParser.parse(pContent);
158
+ }
159
+ catch (pError)
160
+ {
161
+ return fCallback(new Error(`XML parse error: ${pError.message}`));
162
+ }
163
+
164
+ let tmpRecords;
165
+
166
+ if (tmpRecordPath)
167
+ {
168
+ tmpRecords = this._resolveRecordPath(tmpParsed, tmpRecordPath);
169
+ if (!tmpRecords)
170
+ {
171
+ return fCallback(new Error(`recordPath '${tmpRecordPath}' not found in XML`));
172
+ }
173
+ }
174
+ else
175
+ {
176
+ // Smart extraction: walk tree looking for first array of objects
177
+ tmpRecords = this._extractXMLRecords(tmpParsed);
178
+ }
179
+
180
+ if (!tmpRecords)
181
+ {
182
+ // Wrap the entire parsed result as a single record
183
+ tmpRecords = [tmpParsed];
184
+ }
185
+
186
+ return fCallback(null, tmpRecords);
187
+ }
188
+ }
189
+
190
+ module.exports = MeadowIntegrationFileParserXML;
@@ -0,0 +1,142 @@
1
+ 'use strict';
2
+
3
+ const libFableServiceProviderBase = require('fable-serviceproviderbase');
4
+ const libPath = require('path');
5
+
6
+ const defaultFileParserOptions = (
7
+ {
8
+ format: ''
9
+ });
10
+
11
+ // Extension to format mapping
12
+ const EXTENSION_FORMAT_MAP = (
13
+ {
14
+ '.csv': 'csv',
15
+ '.tsv': 'csv',
16
+ '.txt': 'csv',
17
+ '.json': 'json',
18
+ '.jsonl': 'json',
19
+ '.xlsx': 'xlsx',
20
+ '.xlsm': 'xlsx',
21
+ '.xls': 'xlsx',
22
+ '.xml': 'xml',
23
+ '.fw': 'fixedwidth',
24
+ '.dat': 'fixedwidth'
25
+ });
26
+
27
+ class MeadowIntegrationFileParser extends libFableServiceProviderBase
28
+ {
29
+ constructor(pFable, pOptions, pServiceHash)
30
+ {
31
+ let tmpOptions = Object.assign({}, defaultFileParserOptions, pOptions);
32
+ super(pFable, tmpOptions, pServiceHash);
33
+
34
+ this.serviceType = 'MeadowIntegrationFileParser';
35
+
36
+ // Register sub-parser service types
37
+ this.fable.addAndInstantiateServiceTypeIfNotExists('MeadowIntegrationFileParserCSV', require('./Service-FileParser-CSV.js'));
38
+ this.fable.addAndInstantiateServiceTypeIfNotExists('MeadowIntegrationFileParserJSON', require('./Service-FileParser-JSON.js'));
39
+ this.fable.addAndInstantiateServiceTypeIfNotExists('MeadowIntegrationFileParserXLSX', require('./Service-FileParser-XLSX.js'));
40
+ this.fable.addAndInstantiateServiceTypeIfNotExists('MeadowIntegrationFileParserXML', require('./Service-FileParser-XML.js'));
41
+ this.fable.addAndInstantiateServiceTypeIfNotExists('MeadowIntegrationFileParserFixedWidth', require('./Service-FileParser-FixedWidth.js'));
42
+ }
43
+
44
+ /**
45
+ * Detect the format of a file from its extension, then from a content prefix.
46
+ *
47
+ * @param {string} pFilePath - File path (used for extension detection)
48
+ * @param {string} [pContentPrefix] - First bytes of content for content-based detection
49
+ * @returns {string} Format string: 'csv', 'json', 'xlsx', 'xml', 'fixedwidth'
50
+ */
51
+ detectFormat(pFilePath, pContentPrefix)
52
+ {
53
+ // Extension-based detection
54
+ if (pFilePath && typeof pFilePath === 'string')
55
+ {
56
+ let tmpExt = libPath.extname(pFilePath).toLowerCase();
57
+ if (tmpExt && EXTENSION_FORMAT_MAP[tmpExt])
58
+ {
59
+ return EXTENSION_FORMAT_MAP[tmpExt];
60
+ }
61
+ }
62
+
63
+ // Content-based detection
64
+ if (pContentPrefix && typeof pContentPrefix === 'string')
65
+ {
66
+ let tmpTrimmed = pContentPrefix.trim();
67
+ if (tmpTrimmed.startsWith('[') || tmpTrimmed.startsWith('{'))
68
+ {
69
+ return 'json';
70
+ }
71
+ if (tmpTrimmed.startsWith('<?xml') || tmpTrimmed.startsWith('<'))
72
+ {
73
+ return 'xml';
74
+ }
75
+ }
76
+
77
+ return 'csv';
78
+ }
79
+
80
+ /**
81
+ * Get the appropriate sub-parser service for a given format.
82
+ *
83
+ * @param {string} pFormat - Format string
84
+ * @returns {object} Sub-parser service instance
85
+ */
86
+ _getParser(pFormat)
87
+ {
88
+ switch (pFormat)
89
+ {
90
+ case 'json':
91
+ return this.fable.MeadowIntegrationFileParserJSON;
92
+ case 'xlsx':
93
+ return this.fable.MeadowIntegrationFileParserXLSX;
94
+ case 'xml':
95
+ return this.fable.MeadowIntegrationFileParserXML;
96
+ case 'fixedwidth':
97
+ return this.fable.MeadowIntegrationFileParserFixedWidth;
98
+ case 'csv':
99
+ default:
100
+ return this.fable.MeadowIntegrationFileParserCSV;
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Parse a file using streaming, dispatching to the appropriate sub-parser.
106
+ * Format is determined from options.format, then from file extension, then content.
107
+ *
108
+ * @param {string} pFilePath - Absolute path to the file
109
+ * @param {object} pOptions - Parser options; pOptions.format overrides detection
110
+ * @param {function} pChunkCallback - Called with (pError, pRecords) as records are ready
111
+ * @param {function} pCompletionCallback - Called with (pError, pTotalCount) when done
112
+ */
113
+ parseFile(pFilePath, pOptions, pChunkCallback, pCompletionCallback)
114
+ {
115
+ let tmpOptions = Object.assign({}, this.options, pOptions);
116
+ let tmpFormat = tmpOptions.format ? tmpOptions.format.toLowerCase() : this.detectFormat(pFilePath);
117
+ let tmpParser = this._getParser(tmpFormat);
118
+
119
+ this.fable.log.info(`FileParser: parsing [${pFilePath}] as format [${tmpFormat}]`);
120
+ return tmpParser.parseFile(pFilePath, tmpOptions, pChunkCallback, pCompletionCallback);
121
+ }
122
+
123
+ /**
124
+ * Parse content using a full-array (non-streaming) interface.
125
+ * Format is determined from options.format, then from content prefix detection.
126
+ *
127
+ * @param {string|Buffer} pContent - Raw file content
128
+ * @param {object} pOptions - Parser options; pOptions.format overrides detection
129
+ * @param {function} fCallback - Called with (pError, pRecords)
130
+ */
131
+ parseContent(pContent, pOptions, fCallback)
132
+ {
133
+ let tmpOptions = Object.assign({}, this.options, pOptions);
134
+ let tmpContentPrefix = Buffer.isBuffer(pContent) ? '' : (pContent || '').substring(0, 100);
135
+ let tmpFormat = tmpOptions.format ? tmpOptions.format.toLowerCase() : this.detectFormat('', tmpContentPrefix);
136
+ let tmpParser = this._getParser(tmpFormat);
137
+
138
+ return tmpParser.parseContent(pContent, tmpOptions, fCallback);
139
+ }
140
+ }
141
+
142
+ module.exports = MeadowIntegrationFileParser;
@@ -0,0 +1,71 @@
1
+ const MICRODDL_TYPE_MAP =
2
+ {
3
+ '@': { DataType: 'AutoIdentity', Label: 'Auto ID', HasSize: false },
4
+ '%': { DataType: 'GUID', Label: 'GUID', HasSize: false },
5
+ '$': { DataType: 'String', Label: 'String', HasSize: true },
6
+ '*': { DataType: 'Text', Label: 'Text', HasSize: false },
7
+ '#': { DataType: 'Numeric', Label: 'Numeric', HasSize: false },
8
+ '.': { DataType: 'Decimal', Label: 'Decimal', HasSize: true },
9
+ '&': { DataType: 'DateTime', Label: 'Date/Time', HasSize: false },
10
+ '^': { DataType: 'Boolean', Label: 'Boolean', HasSize: false }
11
+ };
12
+
13
+ const DATATYPE_TO_SYMBOL = {};
14
+ for (let tmpSymbol in MICRODDL_TYPE_MAP)
15
+ {
16
+ DATATYPE_TO_SYMBOL[MICRODDL_TYPE_MAP[tmpSymbol].DataType] = tmpSymbol;
17
+ }
18
+
19
+ function columnsToMicroDDL(pColumns, pTableName)
20
+ {
21
+ let tmpTableName = (pTableName || 'Untitled').replace(/[^a-zA-Z0-9_]/g, '');
22
+ let tmpLines = ['!' + tmpTableName];
23
+
24
+ for (let i = 0; i < pColumns.length; i++)
25
+ {
26
+ let tmpCol = pColumns[i];
27
+ let tmpSymbol = DATATYPE_TO_SYMBOL[tmpCol.DataType] || '$';
28
+ let tmpLine = tmpSymbol + (tmpCol.Name || 'Column' + i);
29
+
30
+ if (MICRODDL_TYPE_MAP[tmpSymbol].HasSize && tmpCol.Size)
31
+ {
32
+ tmpLine += ' ' + tmpCol.Size;
33
+ }
34
+
35
+ tmpLines.push(tmpLine);
36
+ }
37
+
38
+ return tmpLines.join('\n');
39
+ }
40
+
41
+ function microDDLToColumns(pDDL)
42
+ {
43
+ let tmpLines = pDDL.split('\n');
44
+ let tmpColumns = [];
45
+
46
+ for (let i = 0; i < tmpLines.length; i++)
47
+ {
48
+ let tmpLine = tmpLines[i].trim();
49
+ if (!tmpLine || tmpLine.startsWith('!') || tmpLine.startsWith('//') || tmpLine.startsWith('--') || tmpLine.startsWith('->'))
50
+ {
51
+ continue;
52
+ }
53
+
54
+ let tmpSymbol = tmpLine.charAt(0);
55
+ if (MICRODDL_TYPE_MAP.hasOwnProperty(tmpSymbol))
56
+ {
57
+ let tmpRest = tmpLine.substring(1).trim();
58
+ let tmpParts = tmpRest.split(/\s+/);
59
+ tmpColumns.push(
60
+ {
61
+ Name: tmpParts[0] || '',
62
+ DataType: MICRODDL_TYPE_MAP[tmpSymbol].DataType,
63
+ Size: tmpParts[1] || ''
64
+ });
65
+ }
66
+ }
67
+
68
+ return tmpColumns;
69
+ }
70
+
71
+ module.exports = { MICRODDL_TYPE_MAP, DATATYPE_TO_SYMBOL, columnsToMicroDDL, microDDLToColumns };