@rokkit/data 1.0.0-next.105 → 1.0.0-next.107
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/package.json +2 -2
- package/src/dataset.js +144 -261
- package/src/index.js +1 -0
- package/src/infer.js +4 -3
- package/src/join.js +152 -0
- package/src/metadata.js +3 -3
- package/src/model.js +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rokkit/data",
|
|
3
|
-
"version": "1.0.0-next.
|
|
3
|
+
"version": "1.0.0-next.107",
|
|
4
4
|
"description": "Contains generic data manipulation functions that can be used in various components.",
|
|
5
5
|
"author": "Jerry Thomas <me@jerrythomas.name>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@lukeed/uuid": "^2.0.1",
|
|
34
|
-
"@rokkit/core": "1.0.0-next.
|
|
34
|
+
"@rokkit/core": "1.0.0-next.106",
|
|
35
35
|
"d3-array": "^3.2.4",
|
|
36
36
|
"d3-collection": "^1.0.7",
|
|
37
37
|
"ramda": "^0.30.1",
|
package/src/dataset.js
CHANGED
|
@@ -1,54 +1,18 @@
|
|
|
1
1
|
import { equals, identity, pick, omit, clone } from 'ramda'
|
|
2
2
|
import { descending } from 'd3-array'
|
|
3
|
-
import { pickAllowedConfig, defaultConfig
|
|
3
|
+
import { pickAllowedConfig, defaultConfig } from './constants'
|
|
4
4
|
import { deriveSortableColumn } from './infer'
|
|
5
5
|
import { groupDataByKeys, fillAlignedData, getAlignGenerator, aggregateData } from './rollup'
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
config.filter = condition
|
|
17
|
-
return config
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
20
|
-
* Groups the dataset by the specified columns.
|
|
21
|
-
* @param {Object} config - The configuration object to be updated.
|
|
22
|
-
* @param {Object} actions - The actions object for method chaining.
|
|
23
|
-
* @param {...string} fields - The columns to group by.
|
|
24
|
-
* @returns {Object} - The updated configurations.
|
|
25
|
-
*/
|
|
26
|
-
function groupBy(config, ...fields) {
|
|
27
|
-
config.group_by = fields
|
|
28
|
-
return config
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Aligns the columns of the dataset using the provided fields.
|
|
33
|
-
*
|
|
34
|
-
* @param {Object} config - The configuration object to be updated.
|
|
35
|
-
* @param {...string} fields - The fields to align.
|
|
36
|
-
* @returns {Object} - The updated configuration.
|
|
37
|
-
*/
|
|
38
|
-
function alignBy(config, ...fields) {
|
|
39
|
-
config.align_by = fields
|
|
40
|
-
return config
|
|
41
|
-
}
|
|
42
|
-
/**
|
|
43
|
-
* Sets the template for adding empty rows in the dataset.
|
|
44
|
-
* @param {Object} config - The configuration object to be updated.
|
|
45
|
-
* @param {Object} template - The template to use for adding empty rows.
|
|
46
|
-
* @returns {Object} - The updated configuration.
|
|
47
|
-
*/
|
|
48
|
-
function usingTemplate(config, template) {
|
|
49
|
-
config.template = template
|
|
50
|
-
return config
|
|
51
|
-
}
|
|
6
|
+
import {
|
|
7
|
+
leftJoin,
|
|
8
|
+
rightJoin,
|
|
9
|
+
fullJoin,
|
|
10
|
+
innerJoin,
|
|
11
|
+
crossJoin,
|
|
12
|
+
semiJoin,
|
|
13
|
+
antiJoin,
|
|
14
|
+
nestedJoin
|
|
15
|
+
} from './join'
|
|
52
16
|
|
|
53
17
|
/**
|
|
54
18
|
* Adds a summary field to the dataset.
|
|
@@ -71,161 +35,6 @@ function summarize(config, from, using) {
|
|
|
71
35
|
return config
|
|
72
36
|
}
|
|
73
37
|
|
|
74
|
-
/**
|
|
75
|
-
* Joins two datasets together based on a condition. Result includes all rows from the first
|
|
76
|
-
* dataset and matching rows from the second dataset. In case of multiple matches, all
|
|
77
|
-
* combinations are returned. When combining the rows, the columns from the first dataset take
|
|
78
|
-
* precedence.
|
|
79
|
-
*
|
|
80
|
-
* inner: only the rows that have a match in both datasets.
|
|
81
|
-
* outer: all rows from the first dataset and matching rows from the second dataset.
|
|
82
|
-
*
|
|
83
|
-
* @param {Array} first - The first dataset to join.
|
|
84
|
-
* @param {Array} second - The second dataset to join.
|
|
85
|
-
* @param {Function} condition - The condition to join the datasets on.
|
|
86
|
-
* @returns {Object} - An object with the inner and outer join result.
|
|
87
|
-
*/
|
|
88
|
-
function joinData(first, second, condition) {
|
|
89
|
-
let inner = []
|
|
90
|
-
const outer = []
|
|
91
|
-
|
|
92
|
-
first.forEach((f) => {
|
|
93
|
-
const matches = second.filter((s) => condition(f, s)).map((m) => ({ ...m, ...f }))
|
|
94
|
-
inner = inner.concat(matches)
|
|
95
|
-
if (matches.length === 0) outer.push(f)
|
|
96
|
-
})
|
|
97
|
-
return { inner, outer }
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Filters the results of the first dataset based on the condition using the second dataset.
|
|
102
|
-
*
|
|
103
|
-
* @param {Array} first - The first dataset to filter.
|
|
104
|
-
* @param {Array} second - The second dataset to filter by.
|
|
105
|
-
* @param {Function} condition - The condition to filter the first dataset by.
|
|
106
|
-
* @returns {Array} - The filtered dataset.
|
|
107
|
-
*/
|
|
108
|
-
function antiJoin(first, second, condition) {
|
|
109
|
-
return first.filter((f) => !second.find((s) => condition(f, s)))
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Joins two datasets together based on a condition. Result includes all rows from the first
|
|
114
|
-
* dataset and matching rows from the second dataset. In case of multiple matches, all
|
|
115
|
-
* combinations are returned. When combining the rows, the columns from the first dataset take
|
|
116
|
-
* precedence.
|
|
117
|
-
*
|
|
118
|
-
* @param {Array} first - The first dataset to join.
|
|
119
|
-
* @param {Array} second - The second dataset to join.
|
|
120
|
-
* @param {Function} condition - The condition to join the datasets on.
|
|
121
|
-
* @returns {Array} - The joined dataset.
|
|
122
|
-
*/
|
|
123
|
-
function innerJoin(first, second, condition) {
|
|
124
|
-
const { inner } = joinData(first, second, condition)
|
|
125
|
-
return inner
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Performs a left join on two datasets based on a condition. Result includes all rows from the first
|
|
130
|
-
* dataset and matching rows from the second dataset. In case of multiple matches, all combinations
|
|
131
|
-
* are returned. When combining the rows, the columns from the first dataset take precedence.
|
|
132
|
-
* If there is no match in the second dataset, only the row from the first dataset is returned.
|
|
133
|
-
*
|
|
134
|
-
* This can be used to perform a right join by swapping the datasets.
|
|
135
|
-
*
|
|
136
|
-
* @param {Array} first - The first dataset to join.
|
|
137
|
-
* @param {Array} second - The second dataset to join.
|
|
138
|
-
* @param {Function} condition - The condition to join the datasets on.
|
|
139
|
-
* @returns {Array} - The joined dataset.
|
|
140
|
-
*/
|
|
141
|
-
function leftJoin(first, second, condition) {
|
|
142
|
-
const { inner, outer } = joinData(first, second, condition)
|
|
143
|
-
return inner.concat(outer)
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* Performs a full join on two datasets based on a condition. Result includes all rows from both
|
|
148
|
-
* datasets. In case of multiple matches, all combinations are returned. When combining the rows,
|
|
149
|
-
* the columns from the first dataset take precedence. If there is no match in the second dataset,
|
|
150
|
-
* only the row from the first dataset is returned. If there is no match in the first dataset, only
|
|
151
|
-
* the row from the second dataset is returned.
|
|
152
|
-
*
|
|
153
|
-
* @param {Array} first - The first dataset to join.
|
|
154
|
-
* @param {Array} second - The second dataset to join.
|
|
155
|
-
* @param {Function} condition - The condition to join the datasets on.
|
|
156
|
-
* @returns {Array} - The joined dataset.
|
|
157
|
-
*/
|
|
158
|
-
function fullJoin(first, second, condition) {
|
|
159
|
-
const { inner, outer } = joinData(first, second, condition)
|
|
160
|
-
const rightOuter = antiJoin(second, first, condition)
|
|
161
|
-
return inner.concat(outer).concat(rightOuter)
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* Performs a cross join on two datasets. Result includes all combinations of rows from both datasets.
|
|
166
|
-
*
|
|
167
|
-
* @param {Array} first - The first dataset to join.
|
|
168
|
-
* @param {Array} second - The second dataset to join.
|
|
169
|
-
* @returns {Array} - The joined dataset.
|
|
170
|
-
*/
|
|
171
|
-
function crossJoin(first, second) {
|
|
172
|
-
return first.map((f) => second.map((s) => ({ ...f, ...s }))).flat()
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* Performs a semi join on two datasets based on a condition. Result includes all rows from the first
|
|
177
|
-
* dataset that have a match in the second dataset.
|
|
178
|
-
*
|
|
179
|
-
* @param {Array} first - The first dataset to join.
|
|
180
|
-
* @param {Array} second - The second dataset to join.
|
|
181
|
-
* @param {Function} condition - The condition to join the datasets on.
|
|
182
|
-
* @returns {Array} - The joined dataset.
|
|
183
|
-
*/
|
|
184
|
-
function semiJoin(first, second, condition) {
|
|
185
|
-
return first.filter((f) => second.find((s) => condition(f, s)))
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* Performs a nested join on two datasets based on a condition. Result includes all rows from the first
|
|
190
|
-
* dataset that have a match in the second dataset. The result is nested with the matching rows from the
|
|
191
|
-
* second dataset.
|
|
192
|
-
*
|
|
193
|
-
* @param {Array} first - The first dataset to join.
|
|
194
|
-
* @param {Array} second - The second dataset to join.
|
|
195
|
-
* @param {Function} condition - The condition to join the datasets on.
|
|
196
|
-
* @param {String} [key='children'] - The key to nest the matching rows under.
|
|
197
|
-
* @returns {Array} - The joined dataset.
|
|
198
|
-
*/
|
|
199
|
-
function nestedJoin(first, second, condition, key = 'children') {
|
|
200
|
-
const result = first.map((f) => ({
|
|
201
|
-
...f,
|
|
202
|
-
[key]: second.filter((s) => condition(f, s))
|
|
203
|
-
}))
|
|
204
|
-
return result
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* Drops the specified keys from the data.
|
|
209
|
-
*
|
|
210
|
-
* @param {Array<Object>} data - The data to drop keys from.
|
|
211
|
-
* @param {Array<String>} keys - The keys to drop.
|
|
212
|
-
* @returns {Array<Object>} - The updated data with the specified keys dropped.
|
|
213
|
-
*/
|
|
214
|
-
function dropKeys(data, ...keys) {
|
|
215
|
-
return data.map((row) => omit(keys, row))
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
/**
|
|
219
|
-
* Deletes rows from the data based on the condition
|
|
220
|
-
*
|
|
221
|
-
* @param {Array<Object>} data - The data to delete rows from.
|
|
222
|
-
* @param {Function} condition - The condition to delete rows on.
|
|
223
|
-
* @returns {Array<Object>} - The updated data with rows deleted based on the condition.
|
|
224
|
-
*/
|
|
225
|
-
function deleteRows(data, condition) {
|
|
226
|
-
return data.filter((row) => !condition(row))
|
|
227
|
-
}
|
|
228
|
-
|
|
229
38
|
/**
|
|
230
39
|
* Updates rows in the data based on the condition
|
|
231
40
|
*
|
|
@@ -315,21 +124,6 @@ function sortDataBy(data, ...fields) {
|
|
|
315
124
|
return data
|
|
316
125
|
}
|
|
317
126
|
|
|
318
|
-
/**
|
|
319
|
-
* Selects the specified columns from the dataset.
|
|
320
|
-
*
|
|
321
|
-
* @param {Array<Object>} data - The data to select from.
|
|
322
|
-
* @param {Object} config - The configuration object.
|
|
323
|
-
* @param {Function} config.filter - The filter function to apply to the data.
|
|
324
|
-
* @param {Array<String>} cols - The columns to select.
|
|
325
|
-
* @returns {Array<Object>} The selected data.
|
|
326
|
-
*/
|
|
327
|
-
function selectKeys(data, config, ...cols) {
|
|
328
|
-
const result = config.filter ? data.filter(config.filter) : data
|
|
329
|
-
if (cols.length > 0) return result.map(pick(cols))
|
|
330
|
-
return result
|
|
331
|
-
}
|
|
332
|
-
|
|
333
127
|
/**
|
|
334
128
|
* Returns the default aggregator which rolls up the columns other than the group_by columns into a single object.
|
|
335
129
|
*
|
|
@@ -379,51 +173,140 @@ function rollup(data, config) {
|
|
|
379
173
|
return aggregatedData
|
|
380
174
|
}
|
|
381
175
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
176
|
+
export class DataSet {
|
|
177
|
+
#config = clone(defaultConfig)
|
|
178
|
+
#data = []
|
|
179
|
+
|
|
180
|
+
constructor(data) {
|
|
181
|
+
this.#data = data
|
|
182
|
+
}
|
|
183
|
+
get data() {
|
|
184
|
+
return this.#data
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
set data(values) {
|
|
188
|
+
this.#data = values
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// configuration
|
|
192
|
+
set config(props = {}) {
|
|
193
|
+
this.#config = { ...this.#config, ...pickAllowedConfig(props) }
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
override(props) {
|
|
197
|
+
this.#config = { ...this.#config, ...pickAllowedConfig(props) }
|
|
198
|
+
return this
|
|
199
|
+
}
|
|
391
200
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
where: (condition) => dataset(data, where(config, condition)),
|
|
396
|
-
groupBy: (...fields) => dataset(data, groupBy(config, ...fields)),
|
|
397
|
-
alignBy: (...fields) => dataset(data, alignBy(config, ...fields)),
|
|
398
|
-
using: (template) => dataset(data, usingTemplate(config, template)),
|
|
399
|
-
summarize: (from, fields) => dataset(data, summarize(config, from, fields)),
|
|
400
|
-
// alter keys
|
|
401
|
-
rename: (how) => dataset(renameKeys(data, how)),
|
|
402
|
-
drop: (...fields) => dataset(dropKeys(data, ...fields)),
|
|
403
|
-
// alter rows
|
|
404
|
-
sortBy: (...fields) => dataset(sortDataBy(data, ...fields)),
|
|
405
|
-
delete: () => dataset(deleteRows(data, config.filter || includeAll)),
|
|
406
|
-
update: (value) => dataset(updateRows(data, config.filter || identity, value)),
|
|
407
|
-
fillNA: (value) => dataset(fillNA(data, value)),
|
|
408
|
-
// transform data
|
|
409
|
-
apply: (callback) => dataset(data.map(callback)),
|
|
410
|
-
rollup: () => dataset(rollup(data, config)),
|
|
411
|
-
select: (...cols) => selectKeys(data, config, ...cols),
|
|
412
|
-
// set operations
|
|
413
|
-
union: (other) => dataset(data.concat(other)),
|
|
414
|
-
minus: (other) => dataset(data.filter((d) => !other.find((x) => equals(x, d)))),
|
|
415
|
-
intersect: (other) => dataset(data.filter((d) => other.find((x) => equals(x, d)))),
|
|
416
|
-
// joins
|
|
417
|
-
innerJoin: (other, condition) => dataset(innerJoin(data, other.select(), condition)),
|
|
418
|
-
leftJoin: (other, condition) => dataset(leftJoin(data, other.select(), condition)),
|
|
419
|
-
rightJoin: (other, condition) => dataset(leftJoin(other.select(), data, condition)),
|
|
420
|
-
fullJoin: (other, condition) => dataset(fullJoin(data, other.select(), condition)),
|
|
421
|
-
crossJoin: (other) => dataset(crossJoin(data, other.select())),
|
|
422
|
-
semiJoin: (other, condition) => dataset(semiJoin(data, other.select(), condition)),
|
|
423
|
-
antiJoin: (other, condition) => dataset(antiJoin(data, other.select(), condition)),
|
|
424
|
-
nestedJoin: (other, condition) =>
|
|
425
|
-
dataset(nestedJoin(data, other.select(), condition, config.children))
|
|
201
|
+
where(condition) {
|
|
202
|
+
this.#config.filter = condition
|
|
203
|
+
return this
|
|
426
204
|
}
|
|
205
|
+
groupBy(...fields) {
|
|
206
|
+
this.#config.group_by = fields
|
|
207
|
+
return this
|
|
208
|
+
}
|
|
209
|
+
alignBy(...fields) {
|
|
210
|
+
this.#config.align_by = fields
|
|
211
|
+
return this
|
|
212
|
+
}
|
|
213
|
+
usingTemplate(template) {
|
|
214
|
+
this.#config.template = template
|
|
215
|
+
return this
|
|
216
|
+
}
|
|
217
|
+
summarize(from, fields) {
|
|
218
|
+
this.config = summarize(this.#config, from, fields)
|
|
219
|
+
return this
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
rename(how) {
|
|
223
|
+
this.data = renameKeys(this.data, how)
|
|
224
|
+
return this
|
|
225
|
+
}
|
|
226
|
+
drop(...fields) {
|
|
227
|
+
const newDataSet = new DataSet(this.data.map((row) => omit(fields, row)))
|
|
228
|
+
newDataSet.config = this.config
|
|
229
|
+
return newDataSet
|
|
230
|
+
}
|
|
231
|
+
// alter rows
|
|
232
|
+
sortBy(...fields) {
|
|
233
|
+
this.data = sortDataBy(this.data, ...fields)
|
|
234
|
+
return this
|
|
235
|
+
}
|
|
236
|
+
remove() {
|
|
237
|
+
if (this.#config.filter) {
|
|
238
|
+
this.#data = this.data.filter((row) => !this.#config.filter(row))
|
|
239
|
+
} else {
|
|
240
|
+
this.#data = []
|
|
241
|
+
}
|
|
242
|
+
this.#config.filter = undefined
|
|
243
|
+
// this.data = deleteRows(this.data, this.#config.filter || includeAll)
|
|
244
|
+
return this
|
|
245
|
+
}
|
|
246
|
+
update(value) {
|
|
247
|
+
this.data = updateRows(this.data, this.#config.filter || identity, value)
|
|
248
|
+
this.#config.filter = undefined
|
|
249
|
+
return this
|
|
250
|
+
}
|
|
251
|
+
fillNA(value) {
|
|
252
|
+
this.data = fillNA(this.#data, value)
|
|
253
|
+
return this
|
|
254
|
+
}
|
|
255
|
+
// transform data
|
|
256
|
+
apply(callback) {
|
|
257
|
+
this.data = this.data.map(callback)
|
|
258
|
+
return this
|
|
259
|
+
}
|
|
260
|
+
rollup() {
|
|
261
|
+
return new DataSet(rollup(this.data, this.#config))
|
|
262
|
+
}
|
|
263
|
+
select(...cols) {
|
|
264
|
+
const result = this.#config.filter ? this.data.filter(this.#config.filter) : this.data
|
|
265
|
+
if (cols.length > 0) return result.map(pick(cols))
|
|
266
|
+
return result
|
|
267
|
+
}
|
|
268
|
+
// set operations
|
|
269
|
+
union(other) {
|
|
270
|
+
if (Array.isArray(other)) {
|
|
271
|
+
return new DataSet(this.data.concat(other))
|
|
272
|
+
}
|
|
273
|
+
return new DataSet(this.data.concat(other.data))
|
|
274
|
+
}
|
|
275
|
+
minus(other) {
|
|
276
|
+
return new DataSet(this.data.filter((d) => !other.find((x) => equals(x, d))))
|
|
277
|
+
}
|
|
278
|
+
intersect(other) {
|
|
279
|
+
return new DataSet(this.data.filter((d) => other.find((x) => equals(x, d))))
|
|
280
|
+
}
|
|
281
|
+
// joins
|
|
282
|
+
innerJoin(other, condition) {
|
|
283
|
+
return new DataSet(innerJoin(this.data, other.data, condition))
|
|
284
|
+
}
|
|
285
|
+
leftJoin(other, condition) {
|
|
286
|
+
return new DataSet(leftJoin(this.data, other.data, condition))
|
|
287
|
+
}
|
|
288
|
+
rightJoin(other, condition) {
|
|
289
|
+
return new DataSet(rightJoin(this.data, other.data, condition))
|
|
290
|
+
}
|
|
291
|
+
fullJoin(other, condition) {
|
|
292
|
+
return new DataSet(fullJoin(this.data, other.data, condition))
|
|
293
|
+
}
|
|
294
|
+
crossJoin(other) {
|
|
295
|
+
return new DataSet(crossJoin(this.data, other.data))
|
|
296
|
+
}
|
|
297
|
+
semiJoin(other, condition) {
|
|
298
|
+
return new DataSet(semiJoin(this.data, other.data, condition))
|
|
299
|
+
}
|
|
300
|
+
antiJoin(other, condition) {
|
|
301
|
+
return new DataSet(antiJoin(this.data, other.data, condition))
|
|
302
|
+
}
|
|
303
|
+
nestedJoin(other, condition) {
|
|
304
|
+
return new DataSet(nestedJoin(this.data, other.data, condition, this.#config.children))
|
|
305
|
+
}
|
|
306
|
+
}
|
|
427
307
|
|
|
428
|
-
|
|
308
|
+
export function dataset(data, options = {}) {
|
|
309
|
+
const ds = new DataSet(data)
|
|
310
|
+
ds.config = options
|
|
311
|
+
return ds
|
|
429
312
|
}
|
package/src/index.js
CHANGED
|
@@ -3,5 +3,6 @@ export * from './types'
|
|
|
3
3
|
export { typeOf } from './utils'
|
|
4
4
|
export { renamer } from './renamer'
|
|
5
5
|
export { model } from './model'
|
|
6
|
+
export { innerJoin, leftJoin, rightJoin, fullJoin, crossJoin, antiJoin, semiJoin } from './join'
|
|
6
7
|
export { dataset } from './dataset'
|
|
7
8
|
export { dataview } from './view'
|
package/src/infer.js
CHANGED
|
@@ -95,8 +95,9 @@ export function deriveSortableColumn(value) {
|
|
|
95
95
|
* @param {Array<Object>} data - An array of objects to deep scan and collect sample values.
|
|
96
96
|
* @returns {Object} A sample object consolidating all unique keys with their latest non-null values.
|
|
97
97
|
*/
|
|
98
|
-
export function
|
|
99
|
-
if (
|
|
98
|
+
export function getSample(data, deepScan = true) {
|
|
99
|
+
if (data.length === 0) return {}
|
|
100
|
+
if (!deepScan) return data[0]
|
|
100
101
|
return data.reduce(
|
|
101
102
|
(acc, cur) => ({
|
|
102
103
|
...acc,
|
|
@@ -117,7 +118,7 @@ export function deriveColumns(data, options) {
|
|
|
117
118
|
if (data.length === 0) return []
|
|
118
119
|
|
|
119
120
|
const { scanMode } = { ...defaultViewOptions, ...options }
|
|
120
|
-
const sample = scanMode === 'fast' ? data[0] :
|
|
121
|
+
const sample = scanMode === 'fast' ? data[0] : getSample(data)
|
|
121
122
|
|
|
122
123
|
return deriveColumnProperties(sample, options)
|
|
123
124
|
}
|
package/src/join.js
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Joins two datasets together based on a condition. Result includes all rows from the first
|
|
3
|
+
* dataset and matching rows from the second dataset. In case of multiple matches, all
|
|
4
|
+
* combinations are returned. When combining the rows, the columns from the first dataset take
|
|
5
|
+
* precedence.
|
|
6
|
+
*
|
|
7
|
+
* inner: only the rows that have a match in both datasets.
|
|
8
|
+
* outer: all rows from the first dataset and matching rows from the second dataset.
|
|
9
|
+
*
|
|
10
|
+
* @param {Array<Object>} first - The first dataset to join.
|
|
11
|
+
* @param {Array<Object>} second - The second dataset to join.
|
|
12
|
+
* @param {Function} condition - The condition to join the datasets on.
|
|
13
|
+
* @returns {Array<Object>} - An object with the inner and outer join result.
|
|
14
|
+
*/
|
|
15
|
+
function joinData(first, second, condition) {
|
|
16
|
+
let inner = []
|
|
17
|
+
const outer = []
|
|
18
|
+
|
|
19
|
+
first.forEach((f) => {
|
|
20
|
+
const matches = second.filter((s) => condition(f, s)).map((m) => ({ ...m, ...f }))
|
|
21
|
+
inner = inner.concat(matches)
|
|
22
|
+
if (matches.length === 0) outer.push(f)
|
|
23
|
+
})
|
|
24
|
+
return { inner, outer }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Filters the results of the first dataset based on the condition using the second dataset.
|
|
29
|
+
*
|
|
30
|
+
* @param {Array<Object>} first - The first dataset to filter.
|
|
31
|
+
* @param {Array<Object>} second - The second dataset to filter by.
|
|
32
|
+
* @param {Function} condition - The condition to filter the first dataset by.
|
|
33
|
+
* @returns {Array<Object>} - The filtered dataset.
|
|
34
|
+
*/
|
|
35
|
+
export function antiJoin(first, second, condition) {
|
|
36
|
+
return first.filter((f) => !second.find((s) => condition(f, s)))
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Joins two datasets together based on a condition. Result includes all rows from the first
|
|
41
|
+
* dataset and matching rows from the second dataset. In case of multiple matches, all
|
|
42
|
+
* combinations are returned. When combining the rows, the columns from the first dataset take
|
|
43
|
+
* precedence.
|
|
44
|
+
*
|
|
45
|
+
* @param {Array<Object>} first - The first dataset to join.
|
|
46
|
+
* @param {Array<Object>} second - The second dataset to join.
|
|
47
|
+
* @param {Function} condition - The condition to join the datasets on.
|
|
48
|
+
* @returns {Array<Object>} - The joined dataset.
|
|
49
|
+
*/
|
|
50
|
+
export function innerJoin(first, second, condition) {
|
|
51
|
+
const { inner } = joinData(first, second, condition)
|
|
52
|
+
return inner
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Performs a left join on two datasets based on a condition.
|
|
57
|
+
*
|
|
58
|
+
* - Result includes all rows from the first dataset and matching rows from the second dataset.
|
|
59
|
+
* - In case of multiple matches, all combinations are returned.
|
|
60
|
+
* - When combining the rows, the columns from the first dataset take precedence.
|
|
61
|
+
* - If there is no match in the second dataset, only the row from the first dataset is returned.
|
|
62
|
+
*
|
|
63
|
+
* This can be used to perform a right join by swapping the datasets.
|
|
64
|
+
*
|
|
65
|
+
* @param {Array<Object>} first - The first dataset to join.
|
|
66
|
+
* @param {Array<Object>} second - The second dataset to join.
|
|
67
|
+
* @param {Function} condition - The condition to join the datasets on.
|
|
68
|
+
* @returns {Array<Object>} - The joined dataset.
|
|
69
|
+
*/
|
|
70
|
+
export function leftJoin(first, second, condition) {
|
|
71
|
+
const { inner, outer } = joinData(first, second, condition)
|
|
72
|
+
return inner.concat(outer)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Performs a right join on two datasets based on a condition.
|
|
77
|
+
*
|
|
78
|
+
* - Result includes all rows from both datasets.
|
|
79
|
+
* - In case of multiple matches, all combinations are returned.
|
|
80
|
+
* - When combining the rows, the columns from the second dataset take precedence.
|
|
81
|
+
* - If there is no match in the first dataset, only the row from the second dataset is returned.
|
|
82
|
+
*
|
|
83
|
+
* @param {Array<Object>} first - The first dataset to join.
|
|
84
|
+
* @param {Array<Object>} second - The second dataset to join.
|
|
85
|
+
* @param {Function} condition - The condition to join the datasets on.
|
|
86
|
+
* @returns {Array<Object>} - The joined dataset.
|
|
87
|
+
*/
|
|
88
|
+
export function rightJoin(first, second, condition) {
|
|
89
|
+
return leftJoin(second, first, condition)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Performs a full join on two datasets based on a condition.
|
|
94
|
+
*
|
|
95
|
+
* - Result includes all rows from both datasets.
|
|
96
|
+
* - In case of multiple matches, all combinations are returned.
|
|
97
|
+
* - When combining the rows, the columns from the second dataset take precedence.
|
|
98
|
+
* - If there is no match in the first dataset, only the row from the second dataset is returned.
|
|
99
|
+
*
|
|
100
|
+
* @param {Array<Object>} first - The first dataset to join.
|
|
101
|
+
* @param {Array<Object>} second - The second dataset to join.
|
|
102
|
+
* @param {Function} condition - The condition to join the datasets on.
|
|
103
|
+
* @returns {Array<Object>} - The joined dataset.
|
|
104
|
+
*/
|
|
105
|
+
export function fullJoin(first, second, condition) {
|
|
106
|
+
const { inner, outer } = joinData(first, second, condition)
|
|
107
|
+
const rightOuter = antiJoin(second, first, condition)
|
|
108
|
+
return inner.concat(outer).concat(rightOuter)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Performs a cross join on two datasets. Result includes all combinations of rows from both datasets.
|
|
113
|
+
*
|
|
114
|
+
* @param {Array<Object>} first - The first dataset to join.
|
|
115
|
+
* @param {Array<Object>} second - The second dataset to join.
|
|
116
|
+
* @returns {Array<Object>} - The joined dataset.
|
|
117
|
+
*/
|
|
118
|
+
export function crossJoin(first, second) {
|
|
119
|
+
return first.map((f) => second.map((s) => ({ ...f, ...s }))).flat()
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Performs a semi join on two datasets based on a condition. Result includes all rows from the first
|
|
124
|
+
* dataset that have a match in the second dataset.
|
|
125
|
+
*
|
|
126
|
+
* @param {Array<Object>} first - The first dataset to join.
|
|
127
|
+
* @param {Array<Object>} second - The second dataset to join.
|
|
128
|
+
* @param {Function} condition - The condition to join the datasets on.
|
|
129
|
+
* @returns {Array<Object>} - The joined dataset.
|
|
130
|
+
*/
|
|
131
|
+
export function semiJoin(first, second, condition) {
|
|
132
|
+
return first.filter((f) => second.find((s) => condition(f, s)))
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Performs a nested join on two datasets based on a condition. Result includes all rows from the first
|
|
137
|
+
* dataset that have a match in the second dataset. The result is nested with the matching rows from the
|
|
138
|
+
* second dataset.
|
|
139
|
+
*
|
|
140
|
+
* @param {Array<Object>} first - The first dataset to join.
|
|
141
|
+
* @param {Array<Object>} second - The second dataset to join.
|
|
142
|
+
* @param {Function} condition - The condition to join the datasets on.
|
|
143
|
+
* @param {String} [key='children'] - The key to nest the matching rows under.
|
|
144
|
+
* @returns {Array<Object>} - The joined dataset.
|
|
145
|
+
*/
|
|
146
|
+
export function nestedJoin(first, second, condition, key = 'children') {
|
|
147
|
+
const result = first.map((f) => ({
|
|
148
|
+
...f,
|
|
149
|
+
[key]: second.filter((s) => condition(f, s))
|
|
150
|
+
}))
|
|
151
|
+
return result
|
|
152
|
+
}
|
package/src/metadata.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { defaultPathOptions } from './constants'
|
|
2
|
-
import {
|
|
2
|
+
import { getSample } from './infer'
|
|
3
3
|
import { typeOf } from './utils'
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -88,8 +88,8 @@ export function deriveColumnProperties(sample, options) {
|
|
|
88
88
|
export function deriveColumnMetadata(data, options = {}) {
|
|
89
89
|
const { metadata, deepScan = false } = options
|
|
90
90
|
if (Array.isArray(metadata) && metadata.length > 0) return metadata
|
|
91
|
-
if (data.length === 0) return []
|
|
92
|
-
const sample =
|
|
91
|
+
// if (data.length === 0) return []
|
|
92
|
+
const sample = getSample(data, deepScan)
|
|
93
93
|
return Object.entries(sample).map(([name, value]) => ({ name, type: typeOf(value) }))
|
|
94
94
|
}
|
|
95
95
|
|
package/src/model.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { identity, clone as copy } from 'ramda'
|
|
2
|
-
import {
|
|
2
|
+
import { getSample } from './infer'
|
|
3
3
|
import { typeOf } from './utils'
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -13,7 +13,7 @@ function deriveModel(data, sparse = false) {
|
|
|
13
13
|
const result = []
|
|
14
14
|
let item = data
|
|
15
15
|
|
|
16
|
-
if (Array.isArray(data)) item = sparse ?
|
|
16
|
+
if (Array.isArray(data)) item = sparse ? getSample(data) : data[0]
|
|
17
17
|
|
|
18
18
|
const kv = Object.entries(item)
|
|
19
19
|
kv.forEach(([key, value]) => {
|