• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

MrSwitch / dare / #72

06 Jun 2026 07:58PM UTC coverage: 69.143% (-30.9%) from 100.0%
#72

push

web-flow
feat(import-engine): move engines out to their own import path, BREAKING CHANGE (#454)

* feat(postgres): move postgres out to it's own endpoint, BREAKING CHANGE

* style(prettier): fix

* style(ts): fix

* test: fixes

* docs: update README

* docs: update README

* fix(import): migrate LIKE keyword to configurable prop

* feat(import): define mysql5.7 (incl 5.6) legacy import paths, #455

* fix(import): test using wrong dependency

* feat: pluggable json formatter

* fix: issue

* chore: address comments

* fix: issue

* fix: address comments

* fix: address comments, tidy ts

* feat(import): abstract json perfix, operator and formatting

* chore: tidy

* fix(import): abstract DML changes

* feat(import): abstract applyAliasesOnUpdate

* feat(import): abstract sql_fulltext_wildcard

* feat(import): abstract sql_fulltext_wildcard

* feat(import): abstract fulltext sign operators

* chore(postgres): migrate to postgres16 for future proofing

* docs(README): add info about engine specific import paths

* Update README.md

* docs(README): update

* fix: address comments

* fix: prettier

339 of 499 branches covered (67.94%)

Branch coverage included in aggregate %.

487 of 516 new or added lines in 6 files covered. (94.38%)

1497 existing lines in 23 files now uncovered.

3439 of 4965 relevant lines covered (69.26%)

4.28 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

22.86
/src/response_handler.js
1
import JSONparse from './utils/JSONparse.js';
9✔
2

9✔
3
// Response
9✔
4
export default function responseHandler(resp) {
9✔
5
        const dareInstance = this;
1✔
6

1✔
7
        // Iterate over the response array and trigger formatting
1✔
8
        return resp.reduce((items, row, index) => {
1✔
9
                const item = responseRowHandler.call(dareInstance, row, index);
1✔
10

1✔
11
                // Push to the out going
1✔
12
                if (typeof item !== 'undefined') {
1✔
13
                        items.push(item);
1✔
14
                }
1✔
15

1✔
16
                return items;
1✔
17
        }, []);
1✔
18
}
1✔
19

9✔
20
export function responseRowHandler(row, index) {
9✔
21
        const dareInstance = this;
1✔
22

1✔
23
        // Expand row...
1✔
24
        let item = formatHandler(row, dareInstance);
1✔
25

1✔
26
        // Add generate fields for generating fields etc...
1✔
27

1✔
28
        this.generated_fields.forEach(obj => {
1✔
UNCOV
29
                // Generate the fields handler
×
UNCOV
30
                generatedFieldsHandler({...obj, target: item}, dareInstance);
×
31
        });
1✔
32

1✔
33
        // Add custom response_row_handler, for handling the record
1✔
34
        if (this.response_row_handler) {
1!
UNCOV
35
                item = this.response_row_handler(item, index);
×
UNCOV
36
        }
×
37
        return item;
1✔
38
}
1✔
39

9✔
40
// Format
9✔
41
function formatHandler(item, dareInstance) {
1✔
42
        // Some of the names were prefixed to ensure uniqueness, e.g., [{name: name, 'asset:name': name}]
1✔
43
        for (const label in item) {
1!
UNCOV
44
                let value = item[label];
×
UNCOV
45

×
UNCOV
46
                // Is this a simple field?
×
UNCOV
47
                if (!(label.includes(',') || label.includes('['))) {
×
UNCOV
48
                        // Is this a very simple field...
×
UNCOV
49
                        if (!label.includes('.')) {
×
UNCOV
50
                                continue; // Dont do anything
×
UNCOV
51
                        }
×
UNCOV
52

×
UNCOV
53
                        // Create new object
×
UNCOV
54
                        explodeKeyValue(item, label.split('.'), value);
×
UNCOV
55
                }
×
UNCOV
56

×
UNCOV
57
                // Does this contain multiples
×
UNCOV
58
                else if (!label.includes('[')) {
×
UNCOV
59
                        /*
×
UNCOV
60
                         * Lets split the value up
×
UNCOV
61
                         */
×
UNCOV
62
                        value = JSONparse(value);
×
UNCOV
63

×
UNCOV
64
                        /*
×
UNCOV
65
                         * Ensure this is an Array
×
UNCOV
66
                         * Subqueries may return NULL if they do not match any records
×
UNCOV
67
                         */
×
UNCOV
68
                        if (Array.isArray(value)) {
×
UNCOV
69
                                label.split(',').forEach((label, index) => {
×
UNCOV
70
                                        explodeKeyValue(item, label.split('.'), value[index]);
×
UNCOV
71
                                });
×
UNCOV
72
                        }
×
UNCOV
73
                } else {
×
UNCOV
74
                        // This has multiple parts
×
UNCOV
75

×
UNCOV
76
                        const m = label.match(/^(?<label>[\s\w$.-]*)\[(?<keys>.*?)]$/i);
×
UNCOV
77

×
UNCOV
78
                        if (!m) {
×
UNCOV
79
                                /*
×
UNCOV
80
                                 * We do not know how to handle this response
×
UNCOV
81
                                 * silently error, return the response as is...
×
UNCOV
82
                                 */
×
UNCOV
83
                                continue;
×
UNCOV
84
                        }
×
UNCOV
85

×
UNCOV
86
                        if (value) {
×
UNCOV
87
                                /*
×
UNCOV
88
                                 * Lets split the value up
×
UNCOV
89
                                 */
×
UNCOV
90
                                value = JSONparse(value);
×
UNCOV
91

×
UNCOV
92
                                /*
×
UNCOV
93
                                 * Create a dummy array
×
UNCOV
94
                                 * And insert into the dataset...
×
UNCOV
95
                                 */
×
UNCOV
96
                                const a = [];
×
UNCOV
97
                                const alabel = m.groups.label;
×
UNCOV
98
                                explodeKeyValue(item, alabel.split('.'), a);
×
UNCOV
99

×
UNCOV
100
                                // Loop through the value entries
×
UNCOV
101
                                const keys = m.groups.keys.split(',');
×
UNCOV
102

×
UNCOV
103
                                if (Array.isArray(value)) {
×
UNCOV
104
                                        value.filter(Boolean).forEach(values => {
×
UNCOV
105
                                                /*
×
UNCOV
106
                                                 * This is a workaround/tidy up for GROUP_CONCAT with JSON_ARRAY/CONCAT_WS
×
UNCOV
107
                                                 * JSON_ARRAY can include a NULL value even if there is no matching join
×
UNCOV
108
                                                 * CONCAT_WS would in the same circumstance return an empty string
×
UNCOV
109
                                                 */
×
UNCOV
110
                                                const emptyValues = dareInstance.engine.startsWith(
×
UNCOV
111
                                                        'mysql:5.6'
×
UNCOV
112
                                                )
×
UNCOV
113
                                                        ? ''
×
UNCOV
114
                                                        : null;
×
UNCOV
115

×
UNCOV
116
                                                if (!values.some(val => val !== emptyValues)) {
×
UNCOV
117
                                                        // Continue
×
UNCOV
118
                                                        return;
×
UNCOV
119
                                                }
×
UNCOV
120

×
UNCOV
121
                                                const obj = {};
×
UNCOV
122
                                                keys.forEach((label, index) => {
×
UNCOV
123
                                                        obj[label] = values[index];
×
UNCOV
124
                                                });
×
UNCOV
125
                                                formatHandler(obj);
×
UNCOV
126
                                                a.push(obj);
×
UNCOV
127
                                        });
×
UNCOV
128
                                }
×
UNCOV
129
                        }
×
UNCOV
130
                }
×
UNCOV
131

×
UNCOV
132
                delete item[label];
×
UNCOV
133
        }
×
134

1✔
135
        return item;
1✔
136
}
1✔
137

9✔
UNCOV
138
function explodeKeyValue(obj, a, value) {
×
UNCOV
139
        // Is this the end?
×
UNCOV
140
        if (a.length === 0) {
×
UNCOV
141
                return value;
×
UNCOV
142
        }
×
UNCOV
143

×
UNCOV
144
        // Clone
×
UNCOV
145
        a = a.slice(0);
×
UNCOV
146

×
UNCOV
147
        // Remove the first element of the array
×
UNCOV
148
        const key = a.shift();
×
UNCOV
149

×
UNCOV
150
        // This is a regular object...
×
UNCOV
151
        if (!(key in obj)) {
×
UNCOV
152
                // Create a new object
×
UNCOV
153
                obj[key] = {};
×
UNCOV
154
        }
×
UNCOV
155

×
UNCOV
156
        // Traverse and Update key value
×
UNCOV
157
        obj[key] = explodeKeyValue(obj[key], a, value);
×
UNCOV
158

×
UNCOV
159
        return obj;
×
UNCOV
160
}
×
161

9✔
162
/**
9✔
163
 * Generate Fields Handler
9✔
164
 * @param {object} obj - Request Object
9✔
165
 * @param {string} obj.label - Name of the property to be created
9✔
166
 * @param {string} obj.field - Name of the property to be created
9✔
167
 * @param {Function} obj.handler - Function to process the request
9✔
168
 * @param {Array} obj.targetAddress - Paths of the target item
9✔
169
 * @param {object} obj.target - Obj where the new prop will be appended
9✔
170
 * @param {object} obj.extraFields - List of fields which can be removed from the response
9✔
171
 * @param {object} dareInstance - Dare Instance
9✔
172
 * @returns {void} Modifies the incoming request with new props
9✔
173
 */
9✔
UNCOV
174
function generatedFieldsHandler(
×
UNCOV
175
        {label, field, handler, targetAddress, target, extraFields = []},
×
UNCOV
176
        dareInstance
×
UNCOV
177
) {
×
UNCOV
178
        if (targetAddress.length === 0) {
×
UNCOV
179
                // Get the handler props
×
UNCOV
180
                const props = getHandlerPropsByAddress(field, target, extraFields);
×
UNCOV
181

×
UNCOV
182
                // Get the item
×
UNCOV
183
                target[label] = handler.call(dareInstance, props);
×
UNCOV
184

×
UNCOV
185
                return;
×
UNCOV
186
        }
×
UNCOV
187

×
UNCOV
188
        // Get the current position in the address
×
UNCOV
189
        const [modelname] = targetAddress;
×
UNCOV
190

×
UNCOV
191
        // Shift the item off the address, creating a new address
×
UNCOV
192
        const next_address = targetAddress.slice(1);
×
UNCOV
193

×
UNCOV
194
        // Get the nested object
×
UNCOV
195
        const nested = target[modelname];
×
UNCOV
196

×
UNCOV
197
        // Assuming it's a valid resource...
×
UNCOV
198
        if (nested) {
×
UNCOV
199
                // And treat single and array items the same for simplicity
×
UNCOV
200
                (Array.isArray(nested) ? nested : [nested]).forEach(target => {
×
UNCOV
201
                        generatedFieldsHandler(
×
UNCOV
202
                                {
×
UNCOV
203
                                        targetAddress: next_address,
×
UNCOV
204
                                        target,
×
UNCOV
205
                                        label,
×
UNCOV
206
                                        field,
×
UNCOV
207
                                        handler,
×
UNCOV
208
                                        extraFields,
×
UNCOV
209
                                },
×
UNCOV
210
                                dareInstance
×
UNCOV
211
                        );
×
UNCOV
212
                });
×
UNCOV
213
        }
×
UNCOV
214
}
×
215

9✔
216
/**
9✔
217
 * GetHandlerPropsByAddress
9✔
218
 *
9✔
219
 * @param {string} requestPath - String denoting the path of the props
9✔
220
 * @param {object} item - Item with the nested props
9✔
221
 * @param {Array} extraFields - extrafields which can be removed afterwards
9✔
222
 * @returns {object} Props object
9✔
223
 */
9✔
UNCOV
224
function getHandlerPropsByAddress(requestPath, item, extraFields) {
×
UNCOV
225
        // Remove the field from the request Path
×
UNCOV
226
        const requestModelAddress = requestPath
×
UNCOV
227
                .slice(0, requestPath.lastIndexOf('.') + 1)
×
UNCOV
228
                .split('.')
×
UNCOV
229
                .filter(Boolean);
×
UNCOV
230

×
UNCOV
231
        // Get the nested object
×
UNCOV
232
        const [props] = getNestedObject(item, requestModelAddress);
×
UNCOV
233

×
UNCOV
234
        // Clone the props...
×
UNCOV
235
        const handler_props = {...props};
×
UNCOV
236

×
UNCOV
237
        // End early is there is no results
×
UNCOV
238
        if (props) {
×
UNCOV
239
                // Remove attributes from the props
×
UNCOV
240
                extraFields.forEach(key => delete props[key]);
×
UNCOV
241
        }
×
UNCOV
242

×
UNCOV
243
        // Walk the object and remove empty objects...
×
UNCOV
244
        removeEmptyObject(item, requestModelAddress);
×
UNCOV
245

×
UNCOV
246
        return handler_props;
×
UNCOV
247
}
×
248

9✔
UNCOV
249
function getNestedObject(obj, address, arr = []) {
×
UNCOV
250
        if (address.length === 0) {
×
UNCOV
251
                arr.push(obj);
×
UNCOV
252
                return arr;
×
UNCOV
253
        }
×
UNCOV
254

×
UNCOV
255
        // Get the current position in the address
×
UNCOV
256
        const [modelname] = address;
×
UNCOV
257

×
UNCOV
258
        // Shift the item off the address, creating a new address
×
UNCOV
259
        const next_address = address.slice(1);
×
UNCOV
260

×
UNCOV
261
        // Get the nested object
×
UNCOV
262
        const nested = obj[modelname];
×
UNCOV
263

×
UNCOV
264
        // Assuming it's a valid resource...
×
UNCOV
265
        if (nested) {
×
UNCOV
266
                // And treat single and array items the same for simplicity
×
UNCOV
267
                (Array.isArray(nested) ? nested : [nested]).forEach(next_obj =>
×
UNCOV
268
                        getNestedObject(next_obj, next_address, arr)
×
UNCOV
269
                );
×
UNCOV
270
        }
×
UNCOV
271

×
UNCOV
272
        return arr;
×
UNCOV
273
}
×
274

9✔
UNCOV
275
function removeEmptyObject(obj, address) {
×
UNCOV
276
        if (address.length === 0) {
×
UNCOV
277
                return Object.keys(obj).length === 0;
×
UNCOV
278
        }
×
UNCOV
279

×
UNCOV
280
        // Get the current position in the address
×
UNCOV
281
        const [modelname] = address;
×
UNCOV
282

×
UNCOV
283
        // Shift the item off the address, creating a new address
×
UNCOV
284
        const next_address = address.slice(1);
×
UNCOV
285

×
UNCOV
286
        // Get the nested object
×
UNCOV
287
        const nested = obj[modelname];
×
UNCOV
288

×
UNCOV
289
        // Assuming it's a valid resource...
×
UNCOV
290
        if (nested) {
×
UNCOV
291
                if (Array.isArray(nested)) {
×
UNCOV
292
                        // Find and remove empties from the array...
×
UNCOV
293
                        obj[modelname] = nested.filter(
×
UNCOV
294
                                next_obj => !removeEmptyObject(next_obj, next_address)
×
UNCOV
295
                        );
×
UNCOV
296

×
UNCOV
297
                        if (obj[modelname].length === 0) {
×
UNCOV
298
                                delete obj[modelname];
×
UNCOV
299
                        }
×
UNCOV
300
                }
×
UNCOV
301

×
UNCOV
302
                if (removeEmptyObject(nested, next_address)) {
×
UNCOV
303
                        delete obj[modelname];
×
UNCOV
304
                }
×
UNCOV
305
        }
×
UNCOV
306

×
UNCOV
307
        return false;
×
UNCOV
308
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc