@rokkit/data 1.0.0-next.95 → 1.0.0-next.97

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rokkit/data",
3
- "version": "1.0.0-next.95",
3
+ "version": "1.0.0-next.97",
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",
@@ -12,15 +12,15 @@
12
12
  "access": "public"
13
13
  },
14
14
  "devDependencies": {
15
- "@sveltejs/vite-plugin-svelte": "^3.0.2",
16
- "@testing-library/svelte": "^4.1.0",
17
- "@types/ramda": "^0.29.12",
18
- "@vitest/coverage-v8": "^1.4.0",
19
- "@vitest/ui": "~1.4.0",
20
- "typescript": "^5.4.4",
21
- "vite": "^5.2.8",
22
- "vitest": "~1.4.0",
23
- "shared-config": "1.0.0-next.95"
15
+ "@sveltejs/vite-plugin-svelte": "^3.1.1",
16
+ "@testing-library/svelte": "^5.1.0",
17
+ "@types/ramda": "^0.30.0",
18
+ "@vitest/coverage-v8": "^1.6.0",
19
+ "@vitest/ui": "~1.6.0",
20
+ "typescript": "^5.4.5",
21
+ "vite": "^5.2.12",
22
+ "vitest": "~1.6.0",
23
+ "shared-config": "1.0.0-next.97"
24
24
  },
25
25
  "files": [
26
26
  "src/**/*.js",
@@ -39,9 +39,9 @@
39
39
  "@lukeed/uuid": "^2.0.1",
40
40
  "d3-array": "^3.2.4",
41
41
  "d3-collection": "^1.0.7",
42
- "ramda": "^0.29.1",
43
- "svelte": "^4.2.12",
44
- "@rokkit/core": "1.0.0-next.95"
42
+ "ramda": "^0.30.1",
43
+ "svelte": "^4.2.17",
44
+ "@rokkit/core": "1.0.0-next.97"
45
45
  },
46
46
  "scripts": {
47
47
  "format": "prettier --write .",
package/src/dataset.js CHANGED
@@ -40,7 +40,7 @@ export function dataset(data, options = {}) {
40
40
  // joins
41
41
  innerJoin: (other, condition) => dataset(innerJoin(data, other.select(), condition)),
42
42
  leftJoin: (other, condition) => dataset(leftJoin(data, other.select(), condition)),
43
- rightJoin: (other, condition) => dataset(rightJoin(data, other.select(), condition)),
43
+ rightJoin: (other, condition) => dataset(leftJoin(other.select(), data, condition)),
44
44
  fullJoin: (other, condition) => dataset(fullJoin(data, other.select(), condition)),
45
45
  crossJoin: (other) => dataset(crossJoin(data, other.select())),
46
46
  semiJoin: (other, condition) => dataset(semiJoin(data, other.select(), condition)),
@@ -179,6 +179,8 @@ function innerJoin(first, second, condition) {
179
179
  * are returned. When combining the rows, the columns from the first dataset take precedence.
180
180
  * If there is no match in the second dataset, only the row from the first dataset is returned.
181
181
  *
182
+ * This can be used to perform a right join by swapping the datasets.
183
+ *
182
184
  * @param {Array} first - The first dataset to join.
183
185
  * @param {Array} second - The second dataset to join.
184
186
  * @param {Function} condition - The condition to join the datasets on.
@@ -189,22 +191,6 @@ function leftJoin(first, second, condition) {
189
191
  return inner.concat(outer)
190
192
  }
191
193
 
192
- /**
193
- * Performs a right join on two datasets based on a condition. Result includes all rows from the second
194
- * dataset and matching rows from the first dataset. In case of multiple matches, all combinations
195
- * are returned. When combining the rows, the columns from the second dataset take precedence.
196
- * If there is no match in the first dataset, only the row from the second dataset is returned.
197
- *
198
- * @param {Array} first - The first dataset to join.
199
- * @param {Array} second - The second dataset to join.
200
- * @param {Function} condition - The condition to join the datasets on.
201
- * @returns {Array} - The joined dataset.
202
- */
203
- function rightJoin(first, second, condition) {
204
- const { inner, outer } = joinData(second, first, condition)
205
- return inner.concat(outer)
206
- }
207
-
208
194
  /**
209
195
  * Performs a full join on two datasets based on a condition. Result includes all rows from both
210
196
  * datasets. In case of multiple matches, all combinations are returned. When combining the rows,
package/src/index.js CHANGED
@@ -1,5 +1,6 @@
1
+ // skipcq: JS-E1004 - Needed for exposing all types
1
2
  export * from './types'
2
- export * from './utils'
3
+ export { typeOf } from './utils'
3
4
  export { renamer } from './renamer'
4
5
  export { model } from './model'
5
6
  export { dataset } from './dataset'
package/src/infer.js CHANGED
@@ -3,7 +3,7 @@ import { compact } from '@rokkit/core'
3
3
  import { omit } from 'ramda'
4
4
  import { createFormatter } from './formatter'
5
5
  import { defaultViewOptions, defaultActionOrder } from './constants'
6
- import { getType } from './utils'
6
+ import { typeOf } from './utils'
7
7
  import { deriveColumnProperties } from './metadata'
8
8
 
9
9
  /**
@@ -14,7 +14,7 @@ import { deriveColumnProperties } from './metadata'
14
14
  * @throws {Error} Throws an error if the value type is not supported or the value format is invalid.
15
15
  */
16
16
  export function deriveSortableColumn(value) {
17
- const type = getType(value)
17
+ const type = typeOf(value)
18
18
 
19
19
  const configGenerators = {
20
20
  array: arrayConfigGenerator,
@@ -166,10 +166,10 @@ export function inferDataType(values) {
166
166
  return 'null'
167
167
  }
168
168
 
169
- let type = getType(nonNullValues[0])
169
+ let type = typeOf(nonNullValues[0])
170
170
 
171
171
  for (let i = 1; i < nonNullValues.length; i++) {
172
- if (getType(nonNullValues[i]) !== type) {
172
+ if (typeOf(nonNullValues[i]) !== type) {
173
173
  type = 'mixed'
174
174
  break
175
175
  }
package/src/metadata.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { defaultPathOptions } from './constants'
2
2
  import { getDeepScanSample } from './infer'
3
- import { getType } from './utils'
3
+ import { typeOf } from './utils'
4
4
 
5
5
  /**
6
6
  * Merges currency-related attributes into the corresponding base columns.
@@ -64,7 +64,7 @@ export function deriveColumnProperties(sample, options) {
64
64
 
65
65
  let columns = Object.keys(sample).map((key) => ({
66
66
  name: key,
67
- type: getType(sample[key]),
67
+ type: typeOf(sample[key]),
68
68
  sortable: true,
69
69
  filterable: true,
70
70
  sorted: 'none',
@@ -90,7 +90,7 @@ export function deriveColumnMetadata(data, options = {}) {
90
90
  if (Array.isArray(metadata) && metadata.length > 0) return metadata
91
91
  if (data.length === 0) return []
92
92
  const sample = deepScan ? getDeepScanSample(data) : data[0]
93
- return Object.entries(sample).map(([name, value]) => ({ name, type: getType(value) }))
93
+ return Object.entries(sample).map(([name, value]) => ({ name, type: typeOf(value) }))
94
94
  }
95
95
 
96
96
  /**
package/src/model.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { identity, clone as copy } from 'ramda'
2
2
  import { getDeepScanSample } from './infer'
3
- import { getType } from './utils'
3
+ import { typeOf } from './utils'
4
4
 
5
5
  /**
6
6
  * A model is a representation of a dataset
@@ -72,7 +72,7 @@ function deriveModel(data, sparse = false) {
72
72
  kv.forEach(([key, value]) => {
73
73
  result.push({
74
74
  name: key,
75
- type: getType(value)
75
+ type: typeOf(value)
76
76
  })
77
77
  })
78
78
  return result
package/src/parser.js CHANGED
@@ -48,7 +48,8 @@ export function parseFilters(string) {
48
48
  // Iterate over the tokens
49
49
  for (const token of tokens) {
50
50
  // Extract the named groups from the token
51
- let { group, column, operator, value } = token.groups
51
+ const { group, column } = token.groups
52
+ let { operator, value } = token.groups
52
53
  search = search.replace(group, '').trim()
53
54
 
54
55
  operator = replaceOperators(operator)
package/src/utils.js CHANGED
@@ -7,25 +7,30 @@ function isDateString(value) {
7
7
  return !isNaN(Date.parse(value))
8
8
  }
9
9
 
10
+ function deriveTypeFromEmptyValue(value) {
11
+ if (value === undefined || value === null) return 'string'
12
+ return typeof value
13
+ }
10
14
  /**
11
15
  * Infer the type of a value as array, date or the default type
12
16
  * @param {any} value
13
17
  * @returns {string}
14
18
  */
15
- function inferArrayAndDateTypes(value) {
19
+ function deriveArrayAndDateTypes(value) {
16
20
  if (Array.isArray(value)) return 'array'
17
21
  if (value instanceof Date) return 'date'
18
22
 
19
- return typeof value
23
+ return deriveTypeFromEmptyValue(value)
20
24
  }
21
25
  /**
22
26
  * Derive the type of a value
23
27
  * @param {any} value
24
28
  * @returns {string}
25
29
  */
26
- export function getType(value) {
27
- const type = inferArrayAndDateTypes(value)
30
+ export function typeOf(value) {
31
+ const type = deriveArrayAndDateTypes(value)
28
32
  if (type === 'string' && isDateString(value)) return 'date'
29
33
  if (type === 'number' && Number.isInteger(value)) return 'integer'
34
+
30
35
  return type
31
36
  }
package/src/view.js CHANGED
@@ -10,76 +10,24 @@ import {
10
10
  } from './hierarchy'
11
11
 
12
12
  /**
13
- * Creates a view object for managing data presentation with metadata and hierarchy structures,
14
- * as well as providing methods for sorting, selection, and toggling of data expansion.
13
+ * Sorts a nested array of objects based on a group of sorters.
15
14
  *
16
- * @param {Array<Object>} data - The source data array to be presented by the view.
17
- * @param {Object} options - The configuration options used for deriving metadata and hierarchy from the data.
18
- * @returns {import('svelte/store').Writable} An object representing the view, providing methods to manipulate and query the presentation state.
15
+ * @param {Array<Object>} elements - The array of objects to sort.
19
16
  */
20
- export function dataview(data, options) {
21
- const store = writable({
22
- columns: deriveMetadata(data, options),
23
- hierarchy: deriveHierarchy(data, options)
24
- })
25
- let sortGroup = []
26
-
27
- /**
28
- * Sorts the hierarchy based on a specific field name and order.
29
- *
30
- * @param {string} name - The field name to sort by.
31
- * @param {boolean} [ascending=true] - Whether the sort should be in ascending order.
32
- */
33
- const sortBy = (name, ascending = true) => {
34
- sortGroup = [...sortGroup, [name, ascending]]
35
- // change the sort order in the metadata
36
- const { columns, hierarchy } = get(store)
37
- columns.forEach((column) => {
38
- if (column.name === name) {
39
- column.order = ascending ? 'ascending' : 'descending'
17
+ function sortNested(elements, group) {
18
+ elements
19
+ .sort((a, b) => {
20
+ let result = 0
21
+ for (let i = 0; i < group.length && result === 0; i++) {
22
+ result = group[i].sorter(a, b)
23
+ }
24
+ return result
25
+ })
26
+ .forEach((x) => {
27
+ if (Array.isArray(x.children) && x.children.length > 0) {
28
+ sortNested(x.children, group)
40
29
  }
41
30
  })
42
- groupSort(hierarchy, sortGroup)
43
- store.set({ columns, hierarchy })
44
- }
45
-
46
- return {
47
- ...omit(['set', 'update'], store),
48
- // filter: noop,
49
- /**
50
- * Clears the applied sort order from the hierarchy.
51
- * TODO: clearing sort should clear the sort orders in columns
52
- */
53
- clearSort: () => {
54
- sortGroup = []
55
- const { columns } = get(store)
56
- columns.forEach((column) => {
57
- column.order = 'none'
58
- })
59
- // clearSortOrder(metadata)
60
- },
61
- sortBy,
62
- /**
63
- * Toggles the selection state of a data element at the specified index.
64
- *
65
- * @param {number} index - The index of the element in the hierarchy to select or deselect.
66
- */
67
- select: (index) => {
68
- const { hierarchy } = get(store)
69
- toggleSelection(hierarchy[index])
70
- store.update((state) => ({ ...state, hierarchy }))
71
- },
72
- /**
73
- * Toggles the expansion state of a data element at the specified index.
74
- *
75
- * @param {number} index - The index of the element in the hierarchy to expand or collapse.
76
- */
77
- toggle: (index) => {
78
- const { hierarchy } = get(store)
79
- toggleExpansion(hierarchy[index])
80
- store.update((state) => ({ ...state, hierarchy }))
81
- }
82
- }
83
31
  }
84
32
 
85
33
  /**
@@ -100,39 +48,22 @@ export function groupSort(hierarchy, sortGroup) {
100
48
  }
101
49
 
102
50
  /**
103
- * Sorts a nested array of objects based on a group of sorters.
104
- *
105
- * @param {Array<Object>} elements - The array of objects to sort.
106
- */
107
- function sortNested(elements, group) {
108
- elements
109
- .sort((a, b) => {
110
- let result = 0
111
- for (let i = 0; i < group.length && result === 0; i++) {
112
- result = group[i].sorter(a, b)
113
- }
114
- return result
115
- })
116
- .forEach((x) => {
117
- if (Array.isArray(x.children) && x.children.length > 0) {
118
- sortNested(x.children, group)
119
- }
120
- })
121
- }
122
-
123
- /**
124
- * Toggles the selection state of a node in the hierarchy.
125
- * Depending on whether the node to toggle is a parent or child,
126
- * it will either select or deselect all children, or update the parent's selection state based on its children's states.
127
- *
128
- * @param {import('./types').Hierarchy} item - The node to toggle.
51
+ * Sorts the hierarchy based on a specific field name and order.
129
52
  *
53
+ * @param {string} name - The field name to sort by.
54
+ * @param {boolean} [ascending=true] - Whether the sort should be in ascending order.
130
55
  */
131
- export function toggleSelection(item) {
132
- item.selected = item.selected === 'checked' ? 'unchecked' : 'checked'
133
-
134
- updateParents(item)
135
- updateChildren(item)
56
+ const sortHierarchyBy = (store, sortGroup, name, ascending = true) => {
57
+ sortGroup = [...sortGroup, [name, ascending]]
58
+ // change the sort order in the metadata
59
+ const { columns, hierarchy } = get(store)
60
+ columns.forEach((column) => {
61
+ if (column.name === name) {
62
+ column.order = ascending ? 'ascending' : 'descending'
63
+ }
64
+ })
65
+ groupSort(hierarchy, sortGroup)
66
+ store.set({ columns, hierarchy })
136
67
  }
137
68
 
138
69
  /**
@@ -185,3 +116,66 @@ function updateParents(item) {
185
116
  parent = parent.parent
186
117
  }
187
118
  }
119
+
120
+ /**
121
+ * Toggles the selection state of a node in the hierarchy.
122
+ * Depending on whether the node to toggle is a parent or child,
123
+ * it will either select or deselect all children, or update the parent's selection state based on its children's states.
124
+ *
125
+ * @param {import('./types').Hierarchy} item - The node to toggle.
126
+ *
127
+ */
128
+ export function toggleSelection(item) {
129
+ item.selected = item.selected === 'checked' ? 'unchecked' : 'checked'
130
+
131
+ updateParents(item)
132
+ updateChildren(item)
133
+ }
134
+
135
+ /**
136
+ * Toggles the expansion or selection state of a data element at the specified index.
137
+ *
138
+ * @param {import('svelte/store').Writable} store - The store object representing the view.
139
+ * @param {number} index - The index of the element in the hierarchy to expand or collapse.
140
+ * @param {string} mode - The mode to toggle, either 'expansion' or 'selection'.
141
+ */
142
+ function toggleState(store, index, mode) {
143
+ const { hierarchy } = get(store)
144
+ if (mode === 'selection') toggleSelection(hierarchy[index])
145
+ else if (mode === 'expansion') toggleExpansion(hierarchy[index])
146
+ store.update((state) => ({ ...state, hierarchy }))
147
+ }
148
+
149
+ /**
150
+ * Creates a view object for managing data presentation with metadata and hierarchy structures,
151
+ * as well as providing methods for sorting, selection, and toggling of data expansion.
152
+ *
153
+ * @param {Array<Object>} data - The source data array to be presented by the view.
154
+ * @param {Object} options - The configuration options used for deriving metadata and hierarchy from the data.
155
+ * @returns {import('svelte/store').Writable} An object representing the view, providing methods to manipulate and query the presentation state.
156
+ */
157
+ export function dataview(data, options) {
158
+ const store = writable({
159
+ columns: deriveMetadata(data, options),
160
+ hierarchy: deriveHierarchy(data, options)
161
+ })
162
+ let sortGroup = []
163
+
164
+ return {
165
+ ...omit(['set', 'update'], store),
166
+ // filter: noop,
167
+ /**
168
+ * Clears the applied sort order from the hierarchy.
169
+ */
170
+ clearSort: () => {
171
+ sortGroup = []
172
+ store.update((state) => ({
173
+ ...state,
174
+ columns: state.columns.map((column) => ({ ...column, order: 'none' }))
175
+ }))
176
+ },
177
+ sortBy: (name, ascending) => sortHierarchyBy(store, sortGroup, name, ascending),
178
+ select: (index) => toggleState(store, index, 'selection'),
179
+ toggle: (index) => toggleState(store, index, 'expansion')
180
+ }
181
+ }