@vuu-ui/vuu-utils 3.4.1 → 3.5.0-beta.1
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 +6 -6
- package/src/column-utils.js +21 -6
- package/src/component-registry.js +2 -1
- package/src/data-utils.js +1 -0
- package/src/datasource/BaseDataSource.js +5 -3
- package/src/date/EpochTimestamp.js +80 -0
- package/src/date/date-utils.js +50 -18
- package/src/date/dateTimePattern.js +17 -5
- package/src/date/formatter.js +54 -16
- package/src/date/index.js +5 -2
- package/src/date/temporal.js +131 -0
- package/src/date/time-zone.js +56 -0
- package/src/filters/filter-utils.js +12 -6
- package/src/filters/filterAsQuery.js +103 -45
- package/src/form-utils.js +38 -4
- package/src/formatting-utils.js +40 -25
- package/src/nanoid/index.js +3 -3
- package/types/column-utils.d.ts +41 -6
- package/types/datasource/BaseDataSource.d.ts +2 -2
- package/types/date/EpochTimestamp.d.ts +63 -0
- package/types/date/date-utils.d.ts +33 -7
- package/types/date/dateTimePattern.d.ts +9 -2
- package/types/date/formatter.d.ts +26 -1
- package/types/date/index.d.ts +5 -1
- package/types/date/temporal.d.ts +68 -0
- package/types/date/time-zone.d.ts +52 -0
- package/types/filters/filter-utils.d.ts +4 -0
- package/types/filters/filterAsQuery.d.ts +38 -0
- package/types/form-utils.d.ts +19 -0
- package/types/formatting-utils.d.ts +15 -0
- package/types/src/Clock.d.ts +0 -20
- package/types/src/PageVisibilityObserver.d.ts +0 -36
- package/types/src/ScaledDecimal.d.ts +0 -12
- package/types/src/ShellContext.d.ts +0 -11
- package/types/src/ThemeProvider.d.ts +0 -32
- package/types/src/array-utils.d.ts +0 -16
- package/types/src/box-utils.d.ts +0 -24
- package/types/src/broadcast-channel.d.ts +0 -16
- package/types/src/column-utils.d.ts +0 -243
- package/types/src/common-types.d.ts +0 -6
- package/types/src/component-registry.d.ts +0 -65
- package/types/src/context-definitions/DataContext.d.ts +0 -21
- package/types/src/context-definitions/DataProvider.d.ts +0 -7
- package/types/src/context-definitions/DataSourceProvider.d.ts +0 -12
- package/types/src/context-definitions/WorkspaceContext.d.ts +0 -17
- package/types/src/cookie-utils.d.ts +0 -9
- package/types/src/css-utils.d.ts +0 -1
- package/types/src/data-utils.d.ts +0 -33
- package/types/src/datasource/BaseDataSource.d.ts +0 -63
- package/types/src/datasource/datasource-action-utils.d.ts +0 -7
- package/types/src/datasource/datasource-filter-utils.d.ts +0 -2
- package/types/src/datasource/datasource-utils.d.ts +0 -61
- package/types/src/date/date-utils.d.ts +0 -39
- package/types/src/date/dateTimePattern.d.ts +0 -8
- package/types/src/date/formatter.d.ts +0 -9
- package/types/src/date/index.d.ts +0 -4
- package/types/src/date/types.d.ts +0 -29
- package/types/src/debug-utils.d.ts +0 -9
- package/types/src/edit-utils.d.ts +0 -2
- package/types/src/errors.d.ts +0 -2
- package/types/src/event-emitter.d.ts +0 -21
- package/types/src/feature-utils.d.ts +0 -110
- package/types/src/filters/filter-utils.d.ts +0 -75
- package/types/src/filters/filterAsQuery.d.ts +0 -8
- package/types/src/filters/index.d.ts +0 -2
- package/types/src/form-utils.d.ts +0 -37
- package/types/src/formatting-utils.d.ts +0 -14
- package/types/src/getUniqueId.d.ts +0 -1
- package/types/src/group-utils.d.ts +0 -10
- package/types/src/html-utils.d.ts +0 -21
- package/types/src/index.d.ts +0 -76
- package/types/src/input-utils.d.ts +0 -2
- package/types/src/invariant.d.ts +0 -1
- package/types/src/itemToString.d.ts +0 -2
- package/types/src/json-types.d.ts +0 -52
- package/types/src/json-utils.d.ts +0 -6
- package/types/src/keyboard-utils.d.ts +0 -15
- package/types/src/keyset.d.ts +0 -16
- package/types/src/layout-types.d.ts +0 -22
- package/types/src/list-utils.d.ts +0 -2
- package/types/src/local-storage-utils.d.ts +0 -4
- package/types/src/logging-utils.d.ts +0 -45
- package/types/src/module-utils.d.ts +0 -8
- package/types/src/nanoid/index.d.ts +0 -1
- package/types/src/perf-utils.d.ts +0 -5
- package/types/src/promise-utils.d.ts +0 -8
- package/types/src/protocol-message-utils.d.ts +0 -43
- package/types/src/range-utils.d.ts +0 -29
- package/types/src/react-utils.d.ts +0 -15
- package/types/src/round-decimal.d.ts +0 -4
- package/types/src/row-utils.d.ts +0 -28
- package/types/src/selection-utils.d.ts +0 -9
- package/types/src/shell-layout-types.d.ts +0 -15
- package/types/src/sort-utils.d.ts +0 -11
- package/types/src/table-schema-utils.d.ts +0 -5
- package/types/src/text-utils.d.ts +0 -10
- package/types/src/theme-utils.d.ts +0 -14
- package/types/src/tree-types.d.ts +0 -9
- package/types/src/tree-utils.d.ts +0 -9
- package/types/src/ts-utils.d.ts +0 -20
- package/types/src/typeahead-utils.d.ts +0 -1
- package/types/src/url-utils.d.ts +0 -3
- package/types/src/useId.d.ts +0 -1
- package/types/src/useLayoutEffectSkipFirst.d.ts +0 -2
- package/types/src/useResizeObserver.d.ts +0 -15
- package/types/src/useStateRef.d.ts +0 -2
- package/types/src/user-types.d.ts +0 -3
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { CalendarDate, CalendarDateTime, fromAbsolute, getLocalTimeZone, toCalendarDate, toZoned } from "@internationalized/date";
|
|
2
|
+
let defaultTimeZone = "local";
|
|
3
|
+
const setDefaultTimeZone = (timeZone)=>{
|
|
4
|
+
defaultTimeZone = timeZone;
|
|
5
|
+
};
|
|
6
|
+
const getDefaultTimeZone = ()=>defaultTimeZone;
|
|
7
|
+
const resolveTimeZone = (timeZone = defaultTimeZone)=>"local" === timeZone ? getLocalTimeZone() : timeZone;
|
|
8
|
+
const getDateTimeFields = (epochMillis, timeZone)=>{
|
|
9
|
+
const { year, month, day, hour, minute, second, millisecond } = fromAbsolute(epochMillis, resolveTimeZone(timeZone));
|
|
10
|
+
return {
|
|
11
|
+
year,
|
|
12
|
+
month,
|
|
13
|
+
day,
|
|
14
|
+
hour,
|
|
15
|
+
minute,
|
|
16
|
+
second,
|
|
17
|
+
millisecond
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
const fromDateTimeFields = ({ year, month, day, hour = 0, minute = 0, second = 0, millisecond = 0 }, timeZone)=>toZoned(new CalendarDateTime(year, month, day, hour, minute, second, millisecond), resolveTimeZone(timeZone), "compatible").toDate().getTime();
|
|
21
|
+
const calendarDateOf = (epochMillis, timeZone)=>toCalendarDate(fromAbsolute(epochMillis, resolveTimeZone(timeZone)));
|
|
22
|
+
const startOfCalendarDay = (date, timeZone)=>toZoned(date, resolveTimeZone(timeZone)).toDate().getTime();
|
|
23
|
+
const startOfDay = (epochMillis, timeZone)=>startOfCalendarDay(calendarDateOf(epochMillis, timeZone), timeZone);
|
|
24
|
+
const startOfNextDay = (epochMillis, timeZone)=>startOfCalendarDay(calendarDateOf(epochMillis, timeZone).add({
|
|
25
|
+
days: 1
|
|
26
|
+
}), timeZone);
|
|
27
|
+
const isStartOfDay = (epochMillis, timeZone)=>startOfDay(epochMillis, timeZone) === epochMillis;
|
|
28
|
+
const isoDatePattern = /^(\d{4})-(\d{2})-(\d{2})$/;
|
|
29
|
+
const resolveRelativeDate = (date = "today", timeZone)=>{
|
|
30
|
+
if (date instanceof Date) return calendarDateOf(date.getTime(), timeZone);
|
|
31
|
+
{
|
|
32
|
+
if ("today" === date) return calendarDateOf(Date.now(), timeZone);
|
|
33
|
+
if ("yesterday" === date) return calendarDateOf(Date.now(), timeZone).subtract({
|
|
34
|
+
days: 1
|
|
35
|
+
});
|
|
36
|
+
const match = isoDatePattern.exec(date);
|
|
37
|
+
if (match) return new CalendarDate(parseInt(match[1]), parseInt(match[2]), parseInt(match[3]));
|
|
38
|
+
throw Error(`[time-zone] invalid date ${date}`);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
const timeOfDayToEpochMillis = (timeString, date = "today", timeZone)=>{
|
|
42
|
+
const { year, month, day } = resolveRelativeDate(date, timeZone);
|
|
43
|
+
const [hms, fraction = ""] = timeString.split(".");
|
|
44
|
+
const [hour, minute, second] = hms.split(":").map((v)=>parseInt(v, 10));
|
|
45
|
+
const millisecond = parseInt(fraction.padEnd(3, "0").slice(0, 3), 10);
|
|
46
|
+
return fromDateTimeFields({
|
|
47
|
+
year,
|
|
48
|
+
month,
|
|
49
|
+
day,
|
|
50
|
+
hour,
|
|
51
|
+
minute,
|
|
52
|
+
second,
|
|
53
|
+
millisecond
|
|
54
|
+
}, timeZone);
|
|
55
|
+
};
|
|
56
|
+
export { fromDateTimeFields, getDateTimeFields, getDefaultTimeZone, isStartOfDay, resolveRelativeDate, resolveTimeZone, setDefaultTimeZone, startOfDay, startOfNextDay, timeOfDayToEpochMillis };
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { ScaledDecimal } from "../ScaledDecimal.js";
|
|
2
|
+
import { isTimeDataValueWithMilliseconds } from "../column-utils.js";
|
|
3
|
+
import { getTemporalInfo } from "../date/temporal.js";
|
|
2
4
|
const singleValueFilterOps = new Set([
|
|
3
5
|
"=",
|
|
4
6
|
"!=",
|
|
@@ -64,6 +66,13 @@ const collectFiltersForColumn = (filter, columnName)=>{
|
|
|
64
66
|
};
|
|
65
67
|
};
|
|
66
68
|
const stringifyBoolean = (value)=>"boolean" == typeof value ? "${filter.value}" : value;
|
|
69
|
+
const getDefaultTimeRange = (column)=>isTimeDataValueWithMilliseconds(column) ? [
|
|
70
|
+
"00:00:00.000",
|
|
71
|
+
"23:59:59.999"
|
|
72
|
+
] : [
|
|
73
|
+
"00:00:00",
|
|
74
|
+
"23:59:59"
|
|
75
|
+
];
|
|
67
76
|
const getColumnValueFromFilter = (column, operator, filter)=>{
|
|
68
77
|
if (isSingleValueFilter(filter)) {
|
|
69
78
|
if (filter.column === column.name) {
|
|
@@ -72,7 +81,7 @@ const getColumnValueFromFilter = (column, operator, filter)=>{
|
|
|
72
81
|
`${filter.value}`,
|
|
73
82
|
""
|
|
74
83
|
];
|
|
75
|
-
else if ("<" === filter.op) return [
|
|
84
|
+
else if ("<" === filter.op || "<=" === filter.op) return [
|
|
76
85
|
"",
|
|
77
86
|
`${filter.value}`
|
|
78
87
|
];
|
|
@@ -97,10 +106,7 @@ const getColumnValueFromFilter = (column, operator, filter)=>{
|
|
|
97
106
|
}
|
|
98
107
|
}
|
|
99
108
|
if (!operator.startsWith("between")) return "";
|
|
100
|
-
if ("time"
|
|
101
|
-
"00:00:00",
|
|
102
|
-
"23:59:59"
|
|
103
|
-
];
|
|
109
|
+
if (getTemporalInfo(column)?.kind === "time") return getDefaultTimeRange(column);
|
|
104
110
|
return [
|
|
105
111
|
"",
|
|
106
112
|
""
|
|
@@ -133,4 +139,4 @@ const filtersAreEqual = (f1, f2)=>{
|
|
|
133
139
|
}
|
|
134
140
|
return false;
|
|
135
141
|
};
|
|
136
|
-
export { applyFilterToColumns, extractFilterForColumn, filtersAreEqual, getColumnValueFromFilter, isAndFilter, isBetweenFilter, isBetweenOperator, isCompleteFilter, isExtendedFilter, isFilter, isFilterClause, isFilteredColumn, isInFilter, isMultiClauseFilter, isMultiValueFilter, isNamedFilter, isOrFilter, isScaledDecimalFilterClause, isSerializableFilter, isSingleValueFilter, isValidFilterClauseOp, stripFilterFromColumns };
|
|
142
|
+
export { applyFilterToColumns, extractFilterForColumn, filtersAreEqual, getColumnValueFromFilter, getDefaultTimeRange, isAndFilter, isBetweenFilter, isBetweenOperator, isCompleteFilter, isExtendedFilter, isFilter, isFilterClause, isFilteredColumn, isInFilter, isMultiClauseFilter, isMultiValueFilter, isNamedFilter, isOrFilter, isScaledDecimalFilterClause, isSerializableFilter, isSingleValueFilter, isValidFilterClauseOp, stripFilterFromColumns };
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { EpochTimestamp } from "../date/EpochTimestamp.js";
|
|
2
|
+
import { getTemporalInfo } from "../date/temporal.js";
|
|
3
|
+
import { isStartOfDay, startOfDay, startOfNextDay, timeOfDayToEpochMillis } from "../date/time-zone.js";
|
|
2
4
|
import { stringIsValidLong } from "../data-utils.js";
|
|
3
5
|
import { isMultiClauseFilter, isMultiValueFilter, isSerializableFilter } from "./filter-utils.js";
|
|
4
6
|
import { ScaledDecimal } from "../ScaledDecimal.js";
|
|
@@ -8,7 +10,9 @@ const filterValue = (value, columnType)=>{
|
|
|
8
10
|
const isNumericType = [
|
|
9
11
|
"int",
|
|
10
12
|
"long",
|
|
11
|
-
"double"
|
|
13
|
+
"double",
|
|
14
|
+
"epochtimestamp",
|
|
15
|
+
"epochtimestampnano"
|
|
12
16
|
].includes(columnType);
|
|
13
17
|
return isNumericType ? value : `"${value}"`;
|
|
14
18
|
}
|
|
@@ -24,64 +28,118 @@ const filterAsQueryCore = (f, opts)=>{
|
|
|
24
28
|
const multiClauseFilter = f.filters.map((filter)=>filterAsQueryCore(filter, opts)).join(` ${f.op} `);
|
|
25
29
|
return `(${multiClauseFilter})`;
|
|
26
30
|
}
|
|
27
|
-
if (isMultiValueFilter(f)) return
|
|
28
|
-
|
|
31
|
+
if (!isMultiValueFilter(f)) return singleValueFilterAsQuery(f, opts);
|
|
32
|
+
{
|
|
33
|
+
const column = opts?.columnsByName?.[f.column];
|
|
34
|
+
const temporalInfo = getTemporalInfo(column);
|
|
35
|
+
const values = f.values;
|
|
36
|
+
if (temporalInfo) {
|
|
37
|
+
const literals = values.map((v)=>EpochTimestamp.fromWire(v, temporalInfo.encoding)?.toLiteral(temporalInfo.encoding) ?? quotedStrings(v));
|
|
38
|
+
return `${f.column} ${f.op} [${literals.join(",")}]`;
|
|
39
|
+
}
|
|
40
|
+
return `${f.column} ${f.op} [${values.map(quotedStrings).join(",")}]`;
|
|
41
|
+
}
|
|
29
42
|
};
|
|
30
43
|
function singleValueFilterAsQuery(f, opts) {
|
|
31
44
|
if (isSerializableFilter(f)) return f.asQuery();
|
|
32
45
|
{
|
|
33
46
|
const column = opts?.columnsByName?.[f.column];
|
|
34
|
-
|
|
47
|
+
const temporalInfo = getTemporalInfo(column);
|
|
48
|
+
if (temporalInfo) return temporalFilterAsQuery(f, temporalInfo);
|
|
35
49
|
return defaultSingleValueFilterAsQuery(f, opts);
|
|
36
50
|
}
|
|
37
51
|
}
|
|
38
52
|
const ONE_DAY_IN_MILLIS = 86400000;
|
|
39
|
-
|
|
40
|
-
|
|
53
|
+
const timeOfDayPattern = /^\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?$/;
|
|
54
|
+
const resolveTemporalFilterValue = (value, { encoding, kind, timeZone }, date = "today")=>{
|
|
55
|
+
if ("string" == typeof value && timeOfDayPattern.test(value)) {
|
|
56
|
+
const [hms, fraction = ""] = value.split(".");
|
|
57
|
+
const millis = timeOfDayToEpochMillis(hms, date, timeZone);
|
|
58
|
+
const nanos = parseInt(fraction.padEnd(9, "0"), 10);
|
|
59
|
+
const ts = EpochTimestamp.fromMillis(millis + Math.floor(nanos / 1000000), "epochNanos" === encoding ? nanos % 1000000 : 0);
|
|
60
|
+
return [
|
|
61
|
+
ts,
|
|
62
|
+
fraction ? "exact" : "second"
|
|
63
|
+
];
|
|
64
|
+
}
|
|
65
|
+
const ts = EpochTimestamp.fromWire(value instanceof ScaledDecimal ? value.asLong : value, encoding);
|
|
66
|
+
if (void 0 === ts) return [
|
|
67
|
+
void 0,
|
|
68
|
+
"exact"
|
|
69
|
+
];
|
|
70
|
+
const isWholeDay = 0 === ts.subMilliNanos && isStartOfDay(ts.epochMillis, timeZone);
|
|
71
|
+
if ("date" === kind || "datetime" === kind && isWholeDay) return [
|
|
72
|
+
ts,
|
|
73
|
+
"day"
|
|
74
|
+
];
|
|
75
|
+
if ("time" === kind && 0 === ts.subMilliNanos && ts.epochMillis % 1000 === 0) return [
|
|
76
|
+
ts,
|
|
77
|
+
"second"
|
|
78
|
+
];
|
|
79
|
+
return [
|
|
80
|
+
ts,
|
|
81
|
+
"exact"
|
|
82
|
+
];
|
|
83
|
+
};
|
|
84
|
+
function temporalFilterAsQuery(f, temporalInfo, options) {
|
|
85
|
+
const { column, op } = f;
|
|
86
|
+
const { encoding, timeZone } = temporalInfo;
|
|
87
|
+
const [ts, granularity] = resolveTemporalFilterValue(f.value, temporalInfo, options?.date);
|
|
88
|
+
if (void 0 === ts) return `${column} ${op} ${filterValue(f.value, "long")}`;
|
|
89
|
+
if ("exact" === granularity) return `${column} ${op} ${ts.toLiteral(encoding)}`;
|
|
90
|
+
const [start, end] = "day" === granularity ? [
|
|
91
|
+
EpochTimestamp.fromMillis(startOfDay(ts.epochMillis, timeZone)),
|
|
92
|
+
EpochTimestamp.fromMillis(startOfNextDay(ts.epochMillis, timeZone))
|
|
93
|
+
] : [
|
|
94
|
+
ts,
|
|
95
|
+
EpochTimestamp.fromMillis(ts.epochMillis + 1000)
|
|
96
|
+
];
|
|
97
|
+
const from = start.toLiteral(encoding);
|
|
98
|
+
const to = end.toLiteral(encoding);
|
|
99
|
+
switch(op){
|
|
41
100
|
case "=":
|
|
42
|
-
{
|
|
43
|
-
const filters = [
|
|
44
|
-
{
|
|
45
|
-
op: ">=",
|
|
46
|
-
column: f.column,
|
|
47
|
-
value: f.value
|
|
48
|
-
},
|
|
49
|
-
{
|
|
50
|
-
op: "<",
|
|
51
|
-
column: f.column,
|
|
52
|
-
value: f.value + ONE_DAY_IN_MILLIS
|
|
53
|
-
}
|
|
54
|
-
];
|
|
55
|
-
return filterAsQueryCore({
|
|
56
|
-
op: "and",
|
|
57
|
-
filters
|
|
58
|
-
}, opts);
|
|
59
|
-
}
|
|
101
|
+
return `(${column} >= ${from} and ${column} < ${to})`;
|
|
60
102
|
case "!=":
|
|
61
|
-
{
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
column: f.column,
|
|
71
|
-
value: f.value + ONE_DAY_IN_MILLIS
|
|
72
|
-
}
|
|
73
|
-
];
|
|
74
|
-
return filterAsQueryCore({
|
|
75
|
-
op: "or",
|
|
76
|
-
filters
|
|
77
|
-
}, opts);
|
|
78
|
-
}
|
|
103
|
+
return `(${column} < ${from} or ${column} >= ${to})`;
|
|
104
|
+
case ">":
|
|
105
|
+
return `${column} >= ${to}`;
|
|
106
|
+
case ">=":
|
|
107
|
+
return `${column} >= ${from}`;
|
|
108
|
+
case "<":
|
|
109
|
+
return `${column} < ${from}`;
|
|
110
|
+
case "<=":
|
|
111
|
+
return `${column} < ${to}`;
|
|
79
112
|
default:
|
|
80
|
-
return
|
|
113
|
+
return `${column} ${op} ${ts.toLiteral(encoding)}`;
|
|
81
114
|
}
|
|
82
115
|
}
|
|
116
|
+
function dateFilterAsQuery(f, opts) {
|
|
117
|
+
const temporalInfo = getTemporalInfo(opts?.columnsByName?.[f.column] ?? {
|
|
118
|
+
type: "date/time"
|
|
119
|
+
});
|
|
120
|
+
return temporalFilterAsQuery(f, temporalInfo);
|
|
121
|
+
}
|
|
83
122
|
const defaultSingleValueFilterAsQuery = (f, opts)=>{
|
|
84
123
|
const columnType = opts?.columnsByName?.[f.column]?.serverDataType;
|
|
85
124
|
return `${f.column} ${f.op} ${filterValue(f.value, columnType)}`;
|
|
86
125
|
};
|
|
87
|
-
|
|
126
|
+
const getColumnsByName = (columns)=>columns.reduce((map, column)=>{
|
|
127
|
+
map[column.name] = column;
|
|
128
|
+
return map;
|
|
129
|
+
}, {});
|
|
130
|
+
const getColumnsByNameForFilter = (tableSchema, columnsByName)=>{
|
|
131
|
+
if (void 0 === tableSchema) return columnsByName;
|
|
132
|
+
const result = {};
|
|
133
|
+
for (const column of tableSchema.columns)result[column.name] = {
|
|
134
|
+
...column,
|
|
135
|
+
...columnsByName?.[column.name]
|
|
136
|
+
};
|
|
137
|
+
if (columnsByName) for (const [name, column] of Object.entries(columnsByName))result[name] ??= column;
|
|
138
|
+
return result;
|
|
139
|
+
};
|
|
140
|
+
const filterRequiresResolution = (filter, columnsByName)=>{
|
|
141
|
+
if (isMultiClauseFilter(filter)) return filter.filters.some((f)=>filterRequiresResolution(f, columnsByName));
|
|
142
|
+
if (isSerializableFilter(filter)) return true;
|
|
143
|
+
return void 0 !== getTemporalInfo(columnsByName?.[filter.column]);
|
|
144
|
+
};
|
|
145
|
+
export { ONE_DAY_IN_MILLIS, dateFilterAsQuery, filterAsQuery, filterRequiresResolution, getColumnsByName, getColumnsByNameForFilter, temporalFilterAsQuery };
|
package/src/form-utils.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { stringIsValidDecimal, stringIsValidInt, stringIsValidLong } from "./data-utils.js";
|
|
2
|
-
import { Time, isValidTimeString } from "./date/
|
|
2
|
+
import { Time, isValidTimeString, isValidTimeStringMillis, normaliseTimeString } from "./date/date-utils.js";
|
|
3
|
+
import { getTemporalInfo, parseTemporalInput } from "./date/temporal.js";
|
|
4
|
+
import { getDefaultTimeZone } from "./date/time-zone.js";
|
|
5
|
+
import { isTypeDescriptor } from "./column-utils.js";
|
|
3
6
|
import { queryClosest } from "./html-utils.js";
|
|
4
7
|
import { ScaledDecimal2, ScaledDecimal4, ScaledDecimal6, ScaledDecimal8 } from "./ScaledDecimal.js";
|
|
5
8
|
class DataValidationError extends Error {
|
|
@@ -19,7 +22,9 @@ const getFieldName = (target)=>{
|
|
|
19
22
|
};
|
|
20
23
|
const isNumber = (type, value)=>"number" === type;
|
|
21
24
|
const isValidRange = ([val1, val2])=>{
|
|
22
|
-
|
|
25
|
+
const time1 = normaliseTimeString(val1, true);
|
|
26
|
+
const time2 = normaliseTimeString(val2, true);
|
|
27
|
+
if (time1 && time2) return time2 > time1;
|
|
23
28
|
return true;
|
|
24
29
|
};
|
|
25
30
|
function getTypedRange([value1, value2], dataType, options) {
|
|
@@ -62,8 +67,24 @@ function getTypedValue(value, type, throwIfInvalid = false, options) {
|
|
|
62
67
|
throw new DataValidationError(`value ${value} is not a valid ${type}`, "decimal", getActualType(value));
|
|
63
68
|
case "boolean":
|
|
64
69
|
return "true" === value;
|
|
70
|
+
case "epochtimestamp":
|
|
71
|
+
case "epochtimestampnano":
|
|
72
|
+
case "date/time":
|
|
73
|
+
case "date":
|
|
74
|
+
{
|
|
75
|
+
const encoding = "epochtimestampnano" === type ? "epochNanos" : "epochMillis";
|
|
76
|
+
const timestamp = parseTemporalInput(value, {
|
|
77
|
+
kind: "date" === type ? "date" : "datetime",
|
|
78
|
+
encoding,
|
|
79
|
+
precision: "epochNanos" === encoding ? "ns" : "ms",
|
|
80
|
+
timeZone: getDefaultTimeZone()
|
|
81
|
+
});
|
|
82
|
+
if (timestamp) return timestamp.toWire(encoding);
|
|
83
|
+
if (!throwIfInvalid) return;
|
|
84
|
+
throw new DataValidationError(`value ${value} is not a valid ${type}`, "date/time", getActualType(value));
|
|
85
|
+
}
|
|
65
86
|
case "time":
|
|
66
|
-
if (isValidTimeString(value)) if (options?.type === "TimeString") return value;
|
|
87
|
+
if (isValidTimeString(value) || isValidTimeStringMillis(value)) if (options?.type === "TimeString") return value;
|
|
67
88
|
else return +Time(value).asDate();
|
|
68
89
|
if (value.length > 0 && Time.isDateInMillis(value)) return Number(value);
|
|
69
90
|
if (!throwIfInvalid) return;
|
|
@@ -72,4 +93,17 @@ function getTypedValue(value, type, throwIfInvalid = false, options) {
|
|
|
72
93
|
return value;
|
|
73
94
|
}
|
|
74
95
|
}
|
|
75
|
-
|
|
96
|
+
function getTypedTemporalValue(value, temporalInfo, throwIfInvalid = false, baseValue) {
|
|
97
|
+
if ("" === value) return;
|
|
98
|
+
const timestamp = parseTemporalInput(value, temporalInfo, baseValue);
|
|
99
|
+
if (timestamp) return timestamp.toWire(temporalInfo.encoding);
|
|
100
|
+
if (throwIfInvalid) throw new DataValidationError(`value ${value} is not a valid ${temporalInfo.kind}`, temporalInfo.kind, getActualType(value));
|
|
101
|
+
}
|
|
102
|
+
function getTypedValueForDescriptor(value, descriptor, throwIfInvalid = false) {
|
|
103
|
+
const temporalInfo = getTemporalInfo(descriptor);
|
|
104
|
+
if (temporalInfo) return getTypedTemporalValue(value, temporalInfo, throwIfInvalid);
|
|
105
|
+
const { serverDataType = "string", type } = descriptor;
|
|
106
|
+
const dataType = isTypeDescriptor(type) ? type.name : type ?? serverDataType;
|
|
107
|
+
return throwIfInvalid ? getTypedValue(value, dataType, true) : getTypedValue(value, dataType, false);
|
|
108
|
+
}
|
|
109
|
+
export { DataValidationError, getFieldName, getTypedRange, getTypedTemporalValue, getTypedValue, getTypedValueForDescriptor, isNumber, isValidRange };
|
package/src/formatting-utils.js
CHANGED
|
@@ -1,31 +1,45 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { dateTimePattern,
|
|
1
|
+
import { isMappedValueTypeRenderer, isTypeDescriptor } from "./column-utils.js";
|
|
2
|
+
import { dateTimePattern, defaultPatternsByType } from "./date/dateTimePattern.js";
|
|
3
|
+
import { isStartOfDay } from "./date/time-zone.js";
|
|
4
|
+
import { EpochTimestamp } from "./date/EpochTimestamp.js";
|
|
5
|
+
import { formatTimestamp } from "./date/formatter.js";
|
|
6
|
+
import { getTemporalInfo } from "./date/temporal.js";
|
|
3
7
|
import { roundDecimal, roundScaledDecimal } from "./round-decimal.js";
|
|
4
8
|
import { isNumericType } from "./protocol-message-utils.js";
|
|
5
9
|
const DEFAULT_NUMERIC_FORMAT = {};
|
|
6
10
|
const defaultValueFormatter = (value)=>null == value ? "" : "string" == typeof value ? value : value.toString();
|
|
7
|
-
const
|
|
8
|
-
if (
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
11
|
+
const temporalFormatter = (column, temporalInfo = getTemporalInfo(column))=>{
|
|
12
|
+
if (void 0 === temporalInfo) return defaultValueFormatter;
|
|
13
|
+
const { encoding, kind, precision, timeZone } = temporalInfo;
|
|
14
|
+
const formatting = isTypeDescriptor(column.type) ? column.type.formatting ?? DEFAULT_NUMERIC_FORMAT : DEFAULT_NUMERIC_FORMAT;
|
|
15
|
+
const pattern = dateTimePattern(column.type, kind);
|
|
16
|
+
const fractionalSecondDigits = formatting.fractionalSecondDigits ?? ("hh:mm:ss.ms" === pattern.time ? 3 : "ns" === precision && pattern.time ? 9 : 0);
|
|
17
|
+
const formatter = formatTimestamp(pattern, {
|
|
18
|
+
fractionalSecondDigits,
|
|
19
|
+
locale: formatting.locale,
|
|
20
|
+
timeZone
|
|
21
|
+
});
|
|
22
|
+
return (value)=>{
|
|
23
|
+
const timestamp = EpochTimestamp.fromWire(value, encoding);
|
|
24
|
+
return timestamp ? formatter(timestamp) : "";
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
const timeOfDayPattern = /^\d{2}:\d{2}:\d{2}(\.\d{1,9})?$/;
|
|
28
|
+
const formatTemporalFilterValue = (value, column)=>{
|
|
29
|
+
const temporalInfo = getTemporalInfo(column);
|
|
30
|
+
if ("string" == typeof value && timeOfDayPattern.test(value)) return value;
|
|
31
|
+
if (void 0 === temporalInfo) return defaultValueFormatter(value);
|
|
32
|
+
const timestamp = EpochTimestamp.fromWire(value, temporalInfo.encoding);
|
|
33
|
+
if (void 0 === timestamp) return defaultValueFormatter(value);
|
|
34
|
+
if ("datetime" === temporalInfo.kind && 0 === timestamp.subMilliNanos && isStartOfDay(timestamp.epochMillis, temporalInfo.timeZone)) {
|
|
35
|
+
const { date = defaultPatternsByType.date } = dateTimePattern(column.type, "date");
|
|
36
|
+
return formatTimestamp({
|
|
37
|
+
date
|
|
38
|
+
}, {
|
|
39
|
+
timeZone: temporalInfo.timeZone
|
|
40
|
+
})(timestamp);
|
|
28
41
|
}
|
|
42
|
+
return temporalFormatter(column, temporalInfo)(value);
|
|
29
43
|
};
|
|
30
44
|
const numericFormatter = ({ align = "right", serverDataType, type })=>{
|
|
31
45
|
if (void 0 === type || "string" == typeof type) return defaultValueFormatter;
|
|
@@ -47,7 +61,8 @@ const NumericTypes = [
|
|
|
47
61
|
];
|
|
48
62
|
const getValueFormatter = (column)=>{
|
|
49
63
|
const { serverDataType = "string" } = column;
|
|
50
|
-
|
|
64
|
+
const temporalInfo = getTemporalInfo(column);
|
|
65
|
+
if (temporalInfo) return temporalFormatter(column, temporalInfo);
|
|
51
66
|
const { type } = column;
|
|
52
67
|
if (isTypeDescriptor(type) && isMappedValueTypeRenderer(type?.renderer)) return mapFormatter(type.renderer.map);
|
|
53
68
|
if (isNumericType(serverDataType) || isTypeDescriptor(type) && NumericTypes.includes(type.name)) return numericFormatter(column);
|
|
@@ -55,4 +70,4 @@ const getValueFormatter = (column)=>{
|
|
|
55
70
|
return defaultValueFormatter;
|
|
56
71
|
};
|
|
57
72
|
const lowerCase = (str)=>str.toLowerCase();
|
|
58
|
-
export { defaultValueFormatter, getValueFormatter, lowerCase, numericFormatter };
|
|
73
|
+
export { defaultValueFormatter, formatTemporalFilterValue, getValueFormatter, lowerCase, numericFormatter, temporalFormatter };
|
package/src/nanoid/index.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
const uuid = (size = 21)=>{
|
|
2
|
-
let id =
|
|
2
|
+
let id = "";
|
|
3
3
|
const bytes = crypto.getRandomValues(new Uint8Array(size));
|
|
4
4
|
while(size--){
|
|
5
5
|
const byte = 63 & bytes[size];
|
|
6
6
|
if (byte < 36) id += byte.toString(36);
|
|
7
7
|
else if (byte < 62) id += (byte - 26).toString(36).toUpperCase();
|
|
8
|
-
else if (byte < 63) id +=
|
|
9
|
-
else id +=
|
|
8
|
+
else if (byte < 63) id += "_";
|
|
9
|
+
else id += "-";
|
|
10
10
|
}
|
|
11
11
|
return id;
|
|
12
12
|
};
|
package/types/column-utils.d.ts
CHANGED
|
@@ -32,13 +32,48 @@ export type CalculatedColumn = {
|
|
|
32
32
|
};
|
|
33
33
|
export declare const isVuuColumnDataType: (value: string | undefined | null) => value is VuuColumnDataType;
|
|
34
34
|
export declare const fromServerDataType: (serverDataType: VuuColumnDataType) => DataValueTypeSimple;
|
|
35
|
+
/**
|
|
36
|
+
* Returns true if serverDataType is epochtimestampnano. Note: prefer
|
|
37
|
+
* getTemporalInfo, which describes encoding, kind and time zone.
|
|
38
|
+
*/
|
|
35
39
|
export declare const isNanoTimestampColumn: ({ serverDataType, }: DataValueDescriptor) => boolean;
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
export declare const
|
|
40
|
+
/**
|
|
41
|
+
* Returns true if serverDataType is epochtimestamp or epochtimestampnano.
|
|
42
|
+
* Note: this does not recognise legacy columns (long with a date/time type),
|
|
43
|
+
* use isTemporalColumn for that.
|
|
44
|
+
*/
|
|
45
|
+
export declare const isTimestampColumn: (column: DataValueDescriptor) => boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Returns true for any column holding a temporal value, see getTemporalInfo.
|
|
48
|
+
*/
|
|
49
|
+
export declare const isTemporalColumn: (column?: DataValueDescriptor) => boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Numeric columns, excluding those that hold temporal values.
|
|
52
|
+
*/
|
|
53
|
+
export declare const isNumericColumn: (column: ColumnDescriptor) => boolean;
|
|
54
|
+
/**
|
|
55
|
+
* Checks the 'type' only, returns true for 'date/time' (or 'date').
|
|
56
|
+
* @deprecated use getTemporalInfo or isDateTimeDataValue
|
|
57
|
+
*/
|
|
58
|
+
export declare const isDateTimeDataType: (column: DataValueDescriptor) => column is DateTimeDataValueDescriptor;
|
|
59
|
+
/**
|
|
60
|
+
* Checks the 'type' only, returns true for 'time'.
|
|
61
|
+
* @deprecated use getTemporalInfo or isTimeDataValue
|
|
62
|
+
*/
|
|
63
|
+
export declare const isTimeDataType: (column: DataValueDescriptor) => column is TimeDataValueDescriptor;
|
|
64
|
+
/**
|
|
65
|
+
* Temporal value of kind 'datetime' or 'date' (i.e has a meaningful date component)
|
|
66
|
+
*/
|
|
67
|
+
export declare const isDateTimeDataValue: (column?: DataValueDescriptor) => boolean;
|
|
68
|
+
/**
|
|
69
|
+
* Temporal value of kind 'time' (time of day)
|
|
70
|
+
*/
|
|
71
|
+
export declare const isTimeDataValue: (column?: DataValueDescriptor) => column is TimeDataValueDescriptor;
|
|
72
|
+
/**
|
|
73
|
+
* A time column is edited/filtered with millisecond precision if its
|
|
74
|
+
* formatting pattern includes milliseconds.
|
|
75
|
+
*/
|
|
76
|
+
export declare const isTimeDataValueWithMilliseconds: (column?: ColumnDescriptor) => column is TimeDataValueDescriptor;
|
|
42
77
|
export declare const isPinned: (column: ColumnDescriptor) => boolean;
|
|
43
78
|
export declare const hasHeadings: (column: ColumnDescriptor) => boolean;
|
|
44
79
|
export declare const isResizing: (column: RuntimeColumnDescriptor) => boolean | undefined;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { DataSource, DataSourceConstructorProps, DataSourceEvents, DataSourceFilter, DataSourceSubscribeCallback, DataSourceSubscribeProps, DataSourceSuspenseProps, WithBaseFilter, WithFullConfig } from "@vuu-ui/vuu-data-types";
|
|
1
|
+
import type { DataSource, DataSourceConstructorProps, DataSourceEvents, DataSourceFilter, DataSourceSubscribeCallback, DataSourceSubscribeProps, DataSourceSuspenseProps, SetFilterOptions, WithBaseFilter, WithFullConfig } from "@vuu-ui/vuu-data-types";
|
|
2
2
|
import { LinkDescriptorWithLabel, VuuAggregation, VuuRange, VuuSort } from "@vuu-ui/vuu-protocol-types";
|
|
3
3
|
import { EventEmitter } from "../event-emitter";
|
|
4
4
|
import { Range } from "../range-utils";
|
|
@@ -28,7 +28,7 @@ export declare abstract class BaseDataSource extends EventEmitter<DataSourceEven
|
|
|
28
28
|
set columns(columns: string[]);
|
|
29
29
|
get filter(): DataSourceFilter;
|
|
30
30
|
set filter(filter: DataSourceFilter);
|
|
31
|
-
setFilter(filter: Filter): void;
|
|
31
|
+
setFilter(filter: Filter, options?: SetFilterOptions): void;
|
|
32
32
|
clearFilter(): void;
|
|
33
33
|
get isAwaitingConfirmationOfConfigChange(): boolean;
|
|
34
34
|
protected confirmConfigChange(): void;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { type DateTimeFields, type TimeZoneSpec } from "./time-zone";
|
|
2
|
+
/**
|
|
3
|
+
* How a temporal value is encoded on the wire (i.e in rows received from,
|
|
4
|
+
* and values sent to, the server).
|
|
5
|
+
* - epochMillis, a number (epochtimestamp, or legacy long with a date/time type)
|
|
6
|
+
* - epochNanos, a string of digits (epochtimestampnano). Nanosecond values
|
|
7
|
+
* cannot be represented safely as JavaScript numbers. A bigint is also
|
|
8
|
+
* accepted where values are generated locally.
|
|
9
|
+
*/
|
|
10
|
+
export type TemporalEncoding = "epochMillis" | "epochNanos";
|
|
11
|
+
/**
|
|
12
|
+
* A timestamp with nanosecond precision. Internally represented as epoch
|
|
13
|
+
* millis (floored) plus nanos within the milli, so the common millisecond
|
|
14
|
+
* case requires no bigint arithmetic.
|
|
15
|
+
*
|
|
16
|
+
* Note: an empty value (null, undefined, '', 0, '0') is treated as 'no value'
|
|
17
|
+
* by fromWire. The Vuu server uses 0 to represent an unset timestamp, so
|
|
18
|
+
* the epoch itself (1970-01-01T00:00:00.000Z) cannot be represented.
|
|
19
|
+
*/
|
|
20
|
+
export declare class EpochTimestamp {
|
|
21
|
+
readonly epochMillis: number;
|
|
22
|
+
/** nanoseconds within the millisecond, 0 - 999,999 */
|
|
23
|
+
readonly subMilliNanos: number;
|
|
24
|
+
private constructor();
|
|
25
|
+
static fromMillis(epochMillis: number, subMilliNanos?: number): EpochTimestamp;
|
|
26
|
+
static fromNanos(epochNanos: bigint | string | number): EpochTimestamp;
|
|
27
|
+
static fromDate(date: Date): EpochTimestamp;
|
|
28
|
+
static fromFields(fields: Partial<DateTimeFields> & Pick<DateTimeFields, "year" | "month" | "day">, timeZone?: TimeZoneSpec, subMilliNanos?: number): EpochTimestamp;
|
|
29
|
+
/**
|
|
30
|
+
* Returns true if value represents 'no value'.
|
|
31
|
+
*/
|
|
32
|
+
static isEmpty(value: unknown): value is 0 | "" | "0" | 0n | null | undefined;
|
|
33
|
+
/**
|
|
34
|
+
* Decode a value as received from the server, or held in a filter,
|
|
35
|
+
* in the given encoding. Returns undefined for an empty or invalid value.
|
|
36
|
+
*/
|
|
37
|
+
static fromWire(value: unknown, encoding: TemporalEncoding): EpochTimestamp | undefined;
|
|
38
|
+
get epochNanos(): bigint;
|
|
39
|
+
toDate(): Date;
|
|
40
|
+
/**
|
|
41
|
+
* Fraction of second, truncated to the requested number of digits
|
|
42
|
+
*/
|
|
43
|
+
fractionalSecond(digits: number): string;
|
|
44
|
+
getFields(timeZone?: TimeZoneSpec): DateTimeFields;
|
|
45
|
+
/**
|
|
46
|
+
* Encode for the wire. epochMillis => number, epochNanos => string
|
|
47
|
+
*/
|
|
48
|
+
toWire(encoding: "epochMillis"): number;
|
|
49
|
+
toWire(encoding: "epochNanos"): string;
|
|
50
|
+
toWire(encoding: TemporalEncoding): number | string;
|
|
51
|
+
/**
|
|
52
|
+
* Encode as a literal in a filter query (never quoted)
|
|
53
|
+
*/
|
|
54
|
+
toLiteral(encoding: TemporalEncoding): string;
|
|
55
|
+
/**
|
|
56
|
+
* Add a number of the smallest unit for the encoding, i.e 1 milli or 1 nano.
|
|
57
|
+
* Useful to convert inclusive to exclusive bounds.
|
|
58
|
+
*/
|
|
59
|
+
addUnits(units: number, encoding: TemporalEncoding): EpochTimestamp;
|
|
60
|
+
compare(other: EpochTimestamp): number;
|
|
61
|
+
equals(other?: EpochTimestamp): boolean;
|
|
62
|
+
toString(): string;
|
|
63
|
+
}
|