@beabee/beabee-common 1.8.1 → 1.8.3
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/dist/cjs/error/InvalidRule.js +11 -0
- package/dist/cjs/error/index.js +8 -0
- package/dist/cjs/index.js +3 -0
- package/dist/cjs/search/index.js +4 -99
- package/dist/cjs/utils/date.js +77 -0
- package/dist/cjs/utils/rules.js +87 -0
- package/dist/esm/error/InvalidRule.js +8 -0
- package/dist/esm/error/index.js +2 -0
- package/dist/esm/index.js +3 -0
- package/dist/esm/search/index.js +2 -88
- package/dist/esm/utils/date.js +68 -0
- package/dist/esm/utils/rules.js +79 -0
- package/dist/types/error/InvalidRule.d.ts +6 -0
- package/dist/types/error/index.d.ts +2 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/search/index.d.ts +21 -30
- package/dist/types/utils/date.d.ts +6 -0
- package/dist/types/utils/rules.d.ts +6 -0
- package/package.json +5 -3
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
class InvalidRule extends Error {
|
|
4
|
+
constructor(rule, message) {
|
|
5
|
+
super();
|
|
6
|
+
this.rule = rule;
|
|
7
|
+
this.message = message;
|
|
8
|
+
Object.setPrototypeOf(this, InvalidRule.prototype);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
exports.default = InvalidRule;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.InvalidRule = void 0;
|
|
7
|
+
const InvalidRule_1 = __importDefault(require("./InvalidRule"));
|
|
8
|
+
exports.InvalidRule = InvalidRule_1.default;
|
package/dist/cjs/index.js
CHANGED
|
@@ -16,3 +16,6 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
__exportStar(require("./search"), exports);
|
|
18
18
|
__exportStar(require("./data"), exports);
|
|
19
|
+
__exportStar(require("./error"), exports);
|
|
20
|
+
__exportStar(require("./utils/date"), exports);
|
|
21
|
+
__exportStar(require("./utils/rules"), exports);
|
package/dist/cjs/search/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
// *** Definitions for rules ***
|
|
2
3
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
4
|
if (k2 === undefined) k2 = k;
|
|
4
5
|
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
@@ -13,14 +14,8 @@ var __createBinding = (this && this.__createBinding) || (Object.create ? (functi
|
|
|
13
14
|
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
15
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
16
|
};
|
|
16
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
17
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
18
|
-
};
|
|
19
17
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
-
exports.
|
|
21
|
-
const isValid_1 = __importDefault(require("date-fns/isValid"));
|
|
22
|
-
const parseISO_1 = __importDefault(require("date-fns/parseISO"));
|
|
23
|
-
// *** Definitions for rules ***
|
|
18
|
+
exports.operatorsByTypeMap = exports.operatorsByType = exports.nullableOperators = exports.ruleOperators = void 0;
|
|
24
19
|
exports.ruleOperators = [
|
|
25
20
|
"equal",
|
|
26
21
|
"not_equal",
|
|
@@ -39,6 +34,7 @@ exports.ruleOperators = [
|
|
|
39
34
|
"is_empty",
|
|
40
35
|
"is_not_empty",
|
|
41
36
|
];
|
|
37
|
+
// *** Operator definitions ***
|
|
42
38
|
const equalityOperators = {
|
|
43
39
|
equal: { args: 1 },
|
|
44
40
|
not_equal: { args: 1 },
|
|
@@ -78,98 +74,7 @@ exports.operatorsByType = {
|
|
|
78
74
|
contact: equalityOperators,
|
|
79
75
|
};
|
|
80
76
|
// More general type to allow mapping while maintaining full type above
|
|
81
|
-
|
|
82
|
-
// *** Helper methods ***
|
|
83
|
-
function isRuleGroup(ruleOrGroup) {
|
|
84
|
-
return "condition" in ruleOrGroup;
|
|
85
|
-
}
|
|
86
|
-
exports.isRuleGroup = isRuleGroup;
|
|
87
|
-
class InvalidRule extends Error {
|
|
88
|
-
constructor(rule, message) {
|
|
89
|
-
super();
|
|
90
|
-
this.rule = rule;
|
|
91
|
-
this.message = message;
|
|
92
|
-
Object.setPrototypeOf(this, InvalidRule.prototype);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
exports.InvalidRule = InvalidRule;
|
|
96
|
-
function validateRule(filters, rule) {
|
|
97
|
-
const filter = filters[rule.field];
|
|
98
|
-
if (!filter) {
|
|
99
|
-
throw new InvalidRule(rule, `Invalid field: ${rule.field}`);
|
|
100
|
-
}
|
|
101
|
-
if (rule.operator in exports.nullableOperators) {
|
|
102
|
-
// Field cannot be empty (except text which can always be empty)
|
|
103
|
-
if (!filter.nullable && filter.type !== "text") {
|
|
104
|
-
throw new InvalidRule(rule, `Invalid nullable operator: field is not nullable`);
|
|
105
|
-
}
|
|
106
|
-
if (rule.value.length !== 0) {
|
|
107
|
-
throw new InvalidRule(rule, `Invalid operator argument count: ${rule.operator} needs 0, ${rule.value.length} given`);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
else {
|
|
111
|
-
const operator = operatorsByTypeMap[filter.type][rule.operator];
|
|
112
|
-
if (!operator) {
|
|
113
|
-
throw new InvalidRule(rule, `Invalid operator for type: ${filter.type} type doesn't define ${rule.operator}`);
|
|
114
|
-
}
|
|
115
|
-
if (operator.args !== rule.value.length) {
|
|
116
|
-
throw new InvalidRule(rule, `Invalid operator argument count: ${rule.operator} needs ${operator.args}, ${rule.value.length} given`);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
const expectedType = filter.type === "boolean" || filter.type === "number"
|
|
120
|
-
? filter.type
|
|
121
|
-
: "string";
|
|
122
|
-
if (rule.value.some((v) => typeof v !== expectedType)) {
|
|
123
|
-
throw new InvalidRule(rule, `Invalid operator argument type: ${rule.operator} needs ${expectedType}, ${rule.value.map((v) => typeof v)} given`);
|
|
124
|
-
}
|
|
125
|
-
if (filter.type === "date" &&
|
|
126
|
-
rule.value.some((v) => !(0, isValid_1.default)((0, parseISO_1.default)(v)))) {
|
|
127
|
-
throw new InvalidRule(rule, `Invalid operator argument: date type needs valid absolute or relative date, ${rule.value} given`);
|
|
128
|
-
}
|
|
129
|
-
return {
|
|
130
|
-
...rule,
|
|
131
|
-
type: filter.type,
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
exports.validateRule = validateRule;
|
|
135
|
-
function validateRuleGroup(filters, ruleGroup) {
|
|
136
|
-
const validatedRuleGroup = {
|
|
137
|
-
condition: ruleGroup.condition,
|
|
138
|
-
rules: [],
|
|
139
|
-
};
|
|
140
|
-
for (const rule of ruleGroup.rules) {
|
|
141
|
-
const valid = isRuleGroup(rule)
|
|
142
|
-
? validateRuleGroup(filters, rule)
|
|
143
|
-
: validateRule(filters, rule);
|
|
144
|
-
validatedRuleGroup.rules.push(valid);
|
|
145
|
-
}
|
|
146
|
-
return validatedRuleGroup;
|
|
147
|
-
}
|
|
148
|
-
exports.validateRuleGroup = validateRuleGroup;
|
|
149
|
-
function convertRuleGroupToFilters(ruleGroup) {
|
|
150
|
-
if (!ruleGroup) {
|
|
151
|
-
return null;
|
|
152
|
-
}
|
|
153
|
-
// TODO: how to handle groups?
|
|
154
|
-
const rulesWithoutGroups = ruleGroup.rules.filter((rule) => !isRuleGroup(rule));
|
|
155
|
-
return rulesWithoutGroups.map((rule) => ({
|
|
156
|
-
id: rule.field,
|
|
157
|
-
operator: rule.operator,
|
|
158
|
-
values: [...rule.value],
|
|
159
|
-
}));
|
|
160
|
-
}
|
|
161
|
-
exports.convertRuleGroupToFilters = convertRuleGroupToFilters;
|
|
162
|
-
function convertFiltersToRuleGroup(matchType, filters) {
|
|
163
|
-
return {
|
|
164
|
-
condition: matchType === "all" ? "AND" : "OR",
|
|
165
|
-
rules: filters.map((filter) => ({
|
|
166
|
-
field: filter.id,
|
|
167
|
-
operator: filter.operator,
|
|
168
|
-
value: filter.values,
|
|
169
|
-
})),
|
|
170
|
-
};
|
|
171
|
-
}
|
|
172
|
-
exports.convertFiltersToRuleGroup = convertFiltersToRuleGroup;
|
|
77
|
+
exports.operatorsByTypeMap = exports.operatorsByType;
|
|
173
78
|
__exportStar(require("./callouts"), exports);
|
|
174
79
|
__exportStar(require("./contacts"), exports);
|
|
175
80
|
__exportStar(require("./notices"), exports);
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.isValidDate = exports.getMinDateUnit = exports.parseDate = void 0;
|
|
7
|
+
const add_1 = __importDefault(require("date-fns/add"));
|
|
8
|
+
const isValid_1 = __importDefault(require("date-fns/isValid"));
|
|
9
|
+
const parseISO_1 = __importDefault(require("date-fns/parseISO"));
|
|
10
|
+
const startOfDay_1 = __importDefault(require("date-fns/startOfDay"));
|
|
11
|
+
const startOfHour_1 = __importDefault(require("date-fns/startOfHour"));
|
|
12
|
+
const startOfMinute_1 = __importDefault(require("date-fns/startOfMinute"));
|
|
13
|
+
const startOfMonth_1 = __importDefault(require("date-fns/startOfMonth"));
|
|
14
|
+
const startOfSecond_1 = __importDefault(require("date-fns/startOfSecond"));
|
|
15
|
+
const startOfYear_1 = __importDefault(require("date-fns/startOfYear"));
|
|
16
|
+
// Must be ordered from highest resolution to lowest (seconds to years)
|
|
17
|
+
const dateUnits = ["s", "m", "h", "d", "M", "y"];
|
|
18
|
+
const dateUnitMap = {
|
|
19
|
+
y: "years",
|
|
20
|
+
M: "months",
|
|
21
|
+
d: "days",
|
|
22
|
+
h: "hours",
|
|
23
|
+
m: "minutes",
|
|
24
|
+
s: "seconds",
|
|
25
|
+
};
|
|
26
|
+
const startOf = {
|
|
27
|
+
y: startOfYear_1.default,
|
|
28
|
+
M: startOfMonth_1.default,
|
|
29
|
+
d: startOfDay_1.default,
|
|
30
|
+
h: startOfHour_1.default,
|
|
31
|
+
m: startOfMinute_1.default,
|
|
32
|
+
s: startOfSecond_1.default,
|
|
33
|
+
};
|
|
34
|
+
const relativeDate = /\$now(?<units>\(((y|M|d|h|m|s):(-?\d+),?)+\))?/;
|
|
35
|
+
const relativeUnit = /(y|M|d|h|m|s):(-?\d+)/g;
|
|
36
|
+
// Matches the different parts of an ISO 8601 date. Note we don't validate the
|
|
37
|
+
// pattern properly as that is handled by parseISO, we just want to know which
|
|
38
|
+
// parts of the date were specified
|
|
39
|
+
const absoluteDate = /^(?<y>\d{4,})(-(?<M>\d\d)(-(?<d>\d\d)([T ](?<h>\d\d)(:(?<m>\d\d)(:(?<s>\d\d))?)?)?)?)?/;
|
|
40
|
+
// Convert relative dates and returns the minimum date unit specified
|
|
41
|
+
function parseDate(value, now) {
|
|
42
|
+
let date;
|
|
43
|
+
let units;
|
|
44
|
+
const relativeMatch = relativeDate.exec(value);
|
|
45
|
+
if (relativeMatch) {
|
|
46
|
+
date = now || new Date();
|
|
47
|
+
const unitsGroup = relativeMatch.groups?.units;
|
|
48
|
+
if (unitsGroup) {
|
|
49
|
+
const unitMatches = unitsGroup.matchAll(relativeUnit);
|
|
50
|
+
units = [];
|
|
51
|
+
for (const [_, unit, delta] of unitMatches) {
|
|
52
|
+
date = (0, add_1.default)(date, { [dateUnitMap[unit]]: Number(delta) });
|
|
53
|
+
units.push(unit);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
units = ["d"];
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
date = (0, parseISO_1.default)(value);
|
|
62
|
+
units = Object.entries(absoluteDate.exec(value)?.groups || {})
|
|
63
|
+
.filter(([_, n]) => !!n)
|
|
64
|
+
.map(([unit]) => unit);
|
|
65
|
+
}
|
|
66
|
+
const minUnit = getMinDateUnit(units) || "s";
|
|
67
|
+
return [startOf[minUnit](date), minUnit];
|
|
68
|
+
}
|
|
69
|
+
exports.parseDate = parseDate;
|
|
70
|
+
function getMinDateUnit(units) {
|
|
71
|
+
return dateUnits.find((unit) => units.includes(unit));
|
|
72
|
+
}
|
|
73
|
+
exports.getMinDateUnit = getMinDateUnit;
|
|
74
|
+
function isValidDate(s) {
|
|
75
|
+
return relativeDate.test(s) || (0, isValid_1.default)((0, parseISO_1.default)(s));
|
|
76
|
+
}
|
|
77
|
+
exports.isValidDate = isValidDate;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.convertFiltersToRuleGroup = exports.convertRuleGroupToFilters = exports.validateRuleGroup = exports.validateRule = exports.isRuleGroup = void 0;
|
|
4
|
+
const error_1 = require("../error");
|
|
5
|
+
const search_1 = require("../search");
|
|
6
|
+
const date_1 = require("./date");
|
|
7
|
+
function isRuleGroup(ruleOrGroup) {
|
|
8
|
+
return "condition" in ruleOrGroup;
|
|
9
|
+
}
|
|
10
|
+
exports.isRuleGroup = isRuleGroup;
|
|
11
|
+
function validateRule(filters, rule) {
|
|
12
|
+
const filter = filters[rule.field];
|
|
13
|
+
if (!filter) {
|
|
14
|
+
throw new error_1.InvalidRule(rule, `Invalid field: ${rule.field}`);
|
|
15
|
+
}
|
|
16
|
+
if (rule.operator in search_1.nullableOperators) {
|
|
17
|
+
// Field cannot be empty (except text which can always be empty)
|
|
18
|
+
if (!filter.nullable && filter.type !== "text") {
|
|
19
|
+
throw new error_1.InvalidRule(rule, `Invalid nullable operator: field is not nullable`);
|
|
20
|
+
}
|
|
21
|
+
if (rule.value.length !== 0) {
|
|
22
|
+
throw new error_1.InvalidRule(rule, `Invalid operator argument count: ${rule.operator} needs 0, ${rule.value.length} given`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
const operator = search_1.operatorsByTypeMap[filter.type][rule.operator];
|
|
27
|
+
if (!operator) {
|
|
28
|
+
throw new error_1.InvalidRule(rule, `Invalid operator for type: ${filter.type} type doesn't define ${rule.operator}`);
|
|
29
|
+
}
|
|
30
|
+
if (operator.args !== rule.value.length) {
|
|
31
|
+
throw new error_1.InvalidRule(rule, `Invalid operator argument count: ${rule.operator} needs ${operator.args}, ${rule.value.length} given`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const expectedType = filter.type === "boolean" || filter.type === "number"
|
|
35
|
+
? filter.type
|
|
36
|
+
: "string";
|
|
37
|
+
if (rule.value.some((v) => typeof v !== expectedType)) {
|
|
38
|
+
throw new error_1.InvalidRule(rule, `Invalid operator argument type: ${rule.operator} needs ${expectedType}, ${rule.value.map((v) => typeof v)} given`);
|
|
39
|
+
}
|
|
40
|
+
if (filter.type === "date" &&
|
|
41
|
+
rule.value.some((v) => !(0, date_1.isValidDate)(v))) {
|
|
42
|
+
throw new error_1.InvalidRule(rule, `Invalid operator argument: date type needs valid absolute or relative date, ${rule.value} given`);
|
|
43
|
+
}
|
|
44
|
+
return {
|
|
45
|
+
...rule,
|
|
46
|
+
type: filter.type,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
exports.validateRule = validateRule;
|
|
50
|
+
function validateRuleGroup(filters, ruleGroup) {
|
|
51
|
+
const validatedRuleGroup = {
|
|
52
|
+
condition: ruleGroup.condition,
|
|
53
|
+
rules: [],
|
|
54
|
+
};
|
|
55
|
+
for (const rule of ruleGroup.rules) {
|
|
56
|
+
const valid = isRuleGroup(rule)
|
|
57
|
+
? validateRuleGroup(filters, rule)
|
|
58
|
+
: validateRule(filters, rule);
|
|
59
|
+
validatedRuleGroup.rules.push(valid);
|
|
60
|
+
}
|
|
61
|
+
return validatedRuleGroup;
|
|
62
|
+
}
|
|
63
|
+
exports.validateRuleGroup = validateRuleGroup;
|
|
64
|
+
function convertRuleGroupToFilters(ruleGroup) {
|
|
65
|
+
if (!ruleGroup) {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
// TODO: how to handle groups?
|
|
69
|
+
const rulesWithoutGroups = ruleGroup.rules.filter((rule) => !isRuleGroup(rule));
|
|
70
|
+
return rulesWithoutGroups.map((rule) => ({
|
|
71
|
+
id: rule.field,
|
|
72
|
+
operator: rule.operator,
|
|
73
|
+
values: [...rule.value],
|
|
74
|
+
}));
|
|
75
|
+
}
|
|
76
|
+
exports.convertRuleGroupToFilters = convertRuleGroupToFilters;
|
|
77
|
+
function convertFiltersToRuleGroup(matchType, filters) {
|
|
78
|
+
return {
|
|
79
|
+
condition: matchType === "all" ? "AND" : "OR",
|
|
80
|
+
rules: filters.map((filter) => ({
|
|
81
|
+
field: filter.id,
|
|
82
|
+
operator: filter.operator,
|
|
83
|
+
value: filter.values,
|
|
84
|
+
})),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
exports.convertFiltersToRuleGroup = convertFiltersToRuleGroup;
|
package/dist/esm/index.js
CHANGED
package/dist/esm/search/index.js
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import isValid from "date-fns/isValid";
|
|
2
|
-
import parseISO from "date-fns/parseISO";
|
|
3
1
|
// *** Definitions for rules ***
|
|
4
2
|
export const ruleOperators = [
|
|
5
3
|
"equal",
|
|
@@ -19,6 +17,7 @@ export const ruleOperators = [
|
|
|
19
17
|
"is_empty",
|
|
20
18
|
"is_not_empty",
|
|
21
19
|
];
|
|
20
|
+
// *** Operator definitions ***
|
|
22
21
|
const equalityOperators = {
|
|
23
22
|
equal: { args: 1 },
|
|
24
23
|
not_equal: { args: 1 },
|
|
@@ -58,92 +57,7 @@ export const operatorsByType = {
|
|
|
58
57
|
contact: equalityOperators,
|
|
59
58
|
};
|
|
60
59
|
// More general type to allow mapping while maintaining full type above
|
|
61
|
-
const operatorsByTypeMap = operatorsByType;
|
|
62
|
-
// *** Helper methods ***
|
|
63
|
-
export function isRuleGroup(ruleOrGroup) {
|
|
64
|
-
return "condition" in ruleOrGroup;
|
|
65
|
-
}
|
|
66
|
-
export class InvalidRule extends Error {
|
|
67
|
-
constructor(rule, message) {
|
|
68
|
-
super();
|
|
69
|
-
this.rule = rule;
|
|
70
|
-
this.message = message;
|
|
71
|
-
Object.setPrototypeOf(this, InvalidRule.prototype);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
export function validateRule(filters, rule) {
|
|
75
|
-
const filter = filters[rule.field];
|
|
76
|
-
if (!filter) {
|
|
77
|
-
throw new InvalidRule(rule, `Invalid field: ${rule.field}`);
|
|
78
|
-
}
|
|
79
|
-
if (rule.operator in nullableOperators) {
|
|
80
|
-
// Field cannot be empty (except text which can always be empty)
|
|
81
|
-
if (!filter.nullable && filter.type !== "text") {
|
|
82
|
-
throw new InvalidRule(rule, `Invalid nullable operator: field is not nullable`);
|
|
83
|
-
}
|
|
84
|
-
if (rule.value.length !== 0) {
|
|
85
|
-
throw new InvalidRule(rule, `Invalid operator argument count: ${rule.operator} needs 0, ${rule.value.length} given`);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
else {
|
|
89
|
-
const operator = operatorsByTypeMap[filter.type][rule.operator];
|
|
90
|
-
if (!operator) {
|
|
91
|
-
throw new InvalidRule(rule, `Invalid operator for type: ${filter.type} type doesn't define ${rule.operator}`);
|
|
92
|
-
}
|
|
93
|
-
if (operator.args !== rule.value.length) {
|
|
94
|
-
throw new InvalidRule(rule, `Invalid operator argument count: ${rule.operator} needs ${operator.args}, ${rule.value.length} given`);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
const expectedType = filter.type === "boolean" || filter.type === "number"
|
|
98
|
-
? filter.type
|
|
99
|
-
: "string";
|
|
100
|
-
if (rule.value.some((v) => typeof v !== expectedType)) {
|
|
101
|
-
throw new InvalidRule(rule, `Invalid operator argument type: ${rule.operator} needs ${expectedType}, ${rule.value.map((v) => typeof v)} given`);
|
|
102
|
-
}
|
|
103
|
-
if (filter.type === "date" &&
|
|
104
|
-
rule.value.some((v) => !isValid(parseISO(v)))) {
|
|
105
|
-
throw new InvalidRule(rule, `Invalid operator argument: date type needs valid absolute or relative date, ${rule.value} given`);
|
|
106
|
-
}
|
|
107
|
-
return {
|
|
108
|
-
...rule,
|
|
109
|
-
type: filter.type,
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
export function validateRuleGroup(filters, ruleGroup) {
|
|
113
|
-
const validatedRuleGroup = {
|
|
114
|
-
condition: ruleGroup.condition,
|
|
115
|
-
rules: [],
|
|
116
|
-
};
|
|
117
|
-
for (const rule of ruleGroup.rules) {
|
|
118
|
-
const valid = isRuleGroup(rule)
|
|
119
|
-
? validateRuleGroup(filters, rule)
|
|
120
|
-
: validateRule(filters, rule);
|
|
121
|
-
validatedRuleGroup.rules.push(valid);
|
|
122
|
-
}
|
|
123
|
-
return validatedRuleGroup;
|
|
124
|
-
}
|
|
125
|
-
export function convertRuleGroupToFilters(ruleGroup) {
|
|
126
|
-
if (!ruleGroup) {
|
|
127
|
-
return null;
|
|
128
|
-
}
|
|
129
|
-
// TODO: how to handle groups?
|
|
130
|
-
const rulesWithoutGroups = ruleGroup.rules.filter((rule) => !isRuleGroup(rule));
|
|
131
|
-
return rulesWithoutGroups.map((rule) => ({
|
|
132
|
-
id: rule.field,
|
|
133
|
-
operator: rule.operator,
|
|
134
|
-
values: [...rule.value],
|
|
135
|
-
}));
|
|
136
|
-
}
|
|
137
|
-
export function convertFiltersToRuleGroup(matchType, filters) {
|
|
138
|
-
return {
|
|
139
|
-
condition: matchType === "all" ? "AND" : "OR",
|
|
140
|
-
rules: filters.map((filter) => ({
|
|
141
|
-
field: filter.id,
|
|
142
|
-
operator: filter.operator,
|
|
143
|
-
value: filter.values,
|
|
144
|
-
})),
|
|
145
|
-
};
|
|
146
|
-
}
|
|
60
|
+
export const operatorsByTypeMap = operatorsByType;
|
|
147
61
|
export * from "./callouts";
|
|
148
62
|
export * from "./contacts";
|
|
149
63
|
export * from "./notices";
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import add from "date-fns/add";
|
|
2
|
+
import isValid from "date-fns/isValid";
|
|
3
|
+
import parseISO from "date-fns/parseISO";
|
|
4
|
+
import startOfDay from "date-fns/startOfDay";
|
|
5
|
+
import startOfHour from "date-fns/startOfHour";
|
|
6
|
+
import startOfMinute from "date-fns/startOfMinute";
|
|
7
|
+
import startOfMonth from "date-fns/startOfMonth";
|
|
8
|
+
import startOfSecond from "date-fns/startOfSecond";
|
|
9
|
+
import startOfYear from "date-fns/startOfYear";
|
|
10
|
+
// Must be ordered from highest resolution to lowest (seconds to years)
|
|
11
|
+
const dateUnits = ["s", "m", "h", "d", "M", "y"];
|
|
12
|
+
const dateUnitMap = {
|
|
13
|
+
y: "years",
|
|
14
|
+
M: "months",
|
|
15
|
+
d: "days",
|
|
16
|
+
h: "hours",
|
|
17
|
+
m: "minutes",
|
|
18
|
+
s: "seconds",
|
|
19
|
+
};
|
|
20
|
+
const startOf = {
|
|
21
|
+
y: startOfYear,
|
|
22
|
+
M: startOfMonth,
|
|
23
|
+
d: startOfDay,
|
|
24
|
+
h: startOfHour,
|
|
25
|
+
m: startOfMinute,
|
|
26
|
+
s: startOfSecond,
|
|
27
|
+
};
|
|
28
|
+
const relativeDate = /\$now(?<units>\(((y|M|d|h|m|s):(-?\d+),?)+\))?/;
|
|
29
|
+
const relativeUnit = /(y|M|d|h|m|s):(-?\d+)/g;
|
|
30
|
+
// Matches the different parts of an ISO 8601 date. Note we don't validate the
|
|
31
|
+
// pattern properly as that is handled by parseISO, we just want to know which
|
|
32
|
+
// parts of the date were specified
|
|
33
|
+
const absoluteDate = /^(?<y>\d{4,})(-(?<M>\d\d)(-(?<d>\d\d)([T ](?<h>\d\d)(:(?<m>\d\d)(:(?<s>\d\d))?)?)?)?)?/;
|
|
34
|
+
// Convert relative dates and returns the minimum date unit specified
|
|
35
|
+
export function parseDate(value, now) {
|
|
36
|
+
let date;
|
|
37
|
+
let units;
|
|
38
|
+
const relativeMatch = relativeDate.exec(value);
|
|
39
|
+
if (relativeMatch) {
|
|
40
|
+
date = now || new Date();
|
|
41
|
+
const unitsGroup = relativeMatch.groups?.units;
|
|
42
|
+
if (unitsGroup) {
|
|
43
|
+
const unitMatches = unitsGroup.matchAll(relativeUnit);
|
|
44
|
+
units = [];
|
|
45
|
+
for (const [_, unit, delta] of unitMatches) {
|
|
46
|
+
date = add(date, { [dateUnitMap[unit]]: Number(delta) });
|
|
47
|
+
units.push(unit);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
units = ["d"];
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
date = parseISO(value);
|
|
56
|
+
units = Object.entries(absoluteDate.exec(value)?.groups || {})
|
|
57
|
+
.filter(([_, n]) => !!n)
|
|
58
|
+
.map(([unit]) => unit);
|
|
59
|
+
}
|
|
60
|
+
const minUnit = getMinDateUnit(units) || "s";
|
|
61
|
+
return [startOf[minUnit](date), minUnit];
|
|
62
|
+
}
|
|
63
|
+
export function getMinDateUnit(units) {
|
|
64
|
+
return dateUnits.find((unit) => units.includes(unit));
|
|
65
|
+
}
|
|
66
|
+
export function isValidDate(s) {
|
|
67
|
+
return relativeDate.test(s) || isValid(parseISO(s));
|
|
68
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { InvalidRule } from "../error";
|
|
2
|
+
import { nullableOperators, operatorsByTypeMap, } from "../search";
|
|
3
|
+
import { isValidDate } from "./date";
|
|
4
|
+
export function isRuleGroup(ruleOrGroup) {
|
|
5
|
+
return "condition" in ruleOrGroup;
|
|
6
|
+
}
|
|
7
|
+
export function validateRule(filters, rule) {
|
|
8
|
+
const filter = filters[rule.field];
|
|
9
|
+
if (!filter) {
|
|
10
|
+
throw new InvalidRule(rule, `Invalid field: ${rule.field}`);
|
|
11
|
+
}
|
|
12
|
+
if (rule.operator in nullableOperators) {
|
|
13
|
+
// Field cannot be empty (except text which can always be empty)
|
|
14
|
+
if (!filter.nullable && filter.type !== "text") {
|
|
15
|
+
throw new InvalidRule(rule, `Invalid nullable operator: field is not nullable`);
|
|
16
|
+
}
|
|
17
|
+
if (rule.value.length !== 0) {
|
|
18
|
+
throw new InvalidRule(rule, `Invalid operator argument count: ${rule.operator} needs 0, ${rule.value.length} given`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
const operator = operatorsByTypeMap[filter.type][rule.operator];
|
|
23
|
+
if (!operator) {
|
|
24
|
+
throw new InvalidRule(rule, `Invalid operator for type: ${filter.type} type doesn't define ${rule.operator}`);
|
|
25
|
+
}
|
|
26
|
+
if (operator.args !== rule.value.length) {
|
|
27
|
+
throw new InvalidRule(rule, `Invalid operator argument count: ${rule.operator} needs ${operator.args}, ${rule.value.length} given`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
const expectedType = filter.type === "boolean" || filter.type === "number"
|
|
31
|
+
? filter.type
|
|
32
|
+
: "string";
|
|
33
|
+
if (rule.value.some((v) => typeof v !== expectedType)) {
|
|
34
|
+
throw new InvalidRule(rule, `Invalid operator argument type: ${rule.operator} needs ${expectedType}, ${rule.value.map((v) => typeof v)} given`);
|
|
35
|
+
}
|
|
36
|
+
if (filter.type === "date" &&
|
|
37
|
+
rule.value.some((v) => !isValidDate(v))) {
|
|
38
|
+
throw new InvalidRule(rule, `Invalid operator argument: date type needs valid absolute or relative date, ${rule.value} given`);
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
...rule,
|
|
42
|
+
type: filter.type,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
export function validateRuleGroup(filters, ruleGroup) {
|
|
46
|
+
const validatedRuleGroup = {
|
|
47
|
+
condition: ruleGroup.condition,
|
|
48
|
+
rules: [],
|
|
49
|
+
};
|
|
50
|
+
for (const rule of ruleGroup.rules) {
|
|
51
|
+
const valid = isRuleGroup(rule)
|
|
52
|
+
? validateRuleGroup(filters, rule)
|
|
53
|
+
: validateRule(filters, rule);
|
|
54
|
+
validatedRuleGroup.rules.push(valid);
|
|
55
|
+
}
|
|
56
|
+
return validatedRuleGroup;
|
|
57
|
+
}
|
|
58
|
+
export function convertRuleGroupToFilters(ruleGroup) {
|
|
59
|
+
if (!ruleGroup) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
// TODO: how to handle groups?
|
|
63
|
+
const rulesWithoutGroups = ruleGroup.rules.filter((rule) => !isRuleGroup(rule));
|
|
64
|
+
return rulesWithoutGroups.map((rule) => ({
|
|
65
|
+
id: rule.field,
|
|
66
|
+
operator: rule.operator,
|
|
67
|
+
values: [...rule.value],
|
|
68
|
+
}));
|
|
69
|
+
}
|
|
70
|
+
export function convertFiltersToRuleGroup(matchType, filters) {
|
|
71
|
+
return {
|
|
72
|
+
condition: matchType === "all" ? "AND" : "OR",
|
|
73
|
+
rules: filters.map((filter) => ({
|
|
74
|
+
field: filter.id,
|
|
75
|
+
operator: filter.operator,
|
|
76
|
+
value: filter.values,
|
|
77
|
+
})),
|
|
78
|
+
};
|
|
79
|
+
}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -25,11 +25,29 @@ export interface ValidatedRuleGroup<Field extends string> {
|
|
|
25
25
|
rules: (ValidatedRuleGroup<Field> | ValidatedRule<Field>)[];
|
|
26
26
|
}
|
|
27
27
|
export declare type FilterType = "text" | "date" | "number" | "boolean" | "array" | "enum" | "contact";
|
|
28
|
-
export declare type FilterValue = RuleValue;
|
|
29
|
-
export declare type FilterOperator = RuleOperator;
|
|
30
28
|
export interface FilterOperatorParams {
|
|
31
29
|
args: number;
|
|
32
30
|
}
|
|
31
|
+
interface BaseFilterArgs {
|
|
32
|
+
type: FilterType;
|
|
33
|
+
nullable?: boolean;
|
|
34
|
+
}
|
|
35
|
+
export interface EnumFilterArgs<T extends readonly string[] = readonly string[]> extends BaseFilterArgs {
|
|
36
|
+
type: "enum";
|
|
37
|
+
options: T;
|
|
38
|
+
}
|
|
39
|
+
export interface OtherFilterArgs extends BaseFilterArgs {
|
|
40
|
+
type: Exclude<FilterType, "enum">;
|
|
41
|
+
}
|
|
42
|
+
export declare type FilterArgs = EnumFilterArgs | OtherFilterArgs;
|
|
43
|
+
export declare type Filters<T extends string = string> = Record<T, FilterArgs>;
|
|
44
|
+
export declare type FilterValue = RuleValue;
|
|
45
|
+
export declare type FilterOperator = RuleOperator;
|
|
46
|
+
export interface Filter {
|
|
47
|
+
id: string;
|
|
48
|
+
operator: FilterOperator;
|
|
49
|
+
values: FilterValue[];
|
|
50
|
+
}
|
|
33
51
|
export declare const nullableOperators: {
|
|
34
52
|
is_empty: {
|
|
35
53
|
args: number;
|
|
@@ -147,24 +165,7 @@ export declare const operatorsByType: {
|
|
|
147
165
|
};
|
|
148
166
|
};
|
|
149
167
|
};
|
|
150
|
-
|
|
151
|
-
type: FilterType;
|
|
152
|
-
nullable?: boolean;
|
|
153
|
-
}
|
|
154
|
-
export interface EnumFilterArgs<T extends readonly string[] = readonly string[]> extends BaseFilterArgs {
|
|
155
|
-
type: "enum";
|
|
156
|
-
options: T;
|
|
157
|
-
}
|
|
158
|
-
export interface OtherFilterArgs extends BaseFilterArgs {
|
|
159
|
-
type: Exclude<FilterType, "enum">;
|
|
160
|
-
}
|
|
161
|
-
export declare type FilterArgs = EnumFilterArgs | OtherFilterArgs;
|
|
162
|
-
export declare type Filters<T extends string = string> = Record<T, FilterArgs>;
|
|
163
|
-
export interface Filter {
|
|
164
|
-
id: string;
|
|
165
|
-
operator: FilterOperator;
|
|
166
|
-
values: FilterValue[];
|
|
167
|
-
}
|
|
168
|
+
export declare const operatorsByTypeMap: Record<FilterType, Partial<Record<FilterOperator, FilterOperatorParams>>>;
|
|
168
169
|
export interface Paginated<T> {
|
|
169
170
|
items: T[];
|
|
170
171
|
offset: number;
|
|
@@ -178,16 +179,6 @@ export interface PaginatedQuery {
|
|
|
178
179
|
order?: "ASC" | "DESC";
|
|
179
180
|
rules?: RuleGroup;
|
|
180
181
|
}
|
|
181
|
-
export declare function isRuleGroup(ruleOrGroup: Rule | RuleGroup): ruleOrGroup is RuleGroup;
|
|
182
|
-
export declare class InvalidRule extends Error {
|
|
183
|
-
readonly rule: Rule;
|
|
184
|
-
readonly message: string;
|
|
185
|
-
constructor(rule: Rule, message: string);
|
|
186
|
-
}
|
|
187
|
-
export declare function validateRule<Field extends string>(filters: Filters<Field>, rule: Rule): ValidatedRule<Field>;
|
|
188
|
-
export declare function validateRuleGroup<Field extends string>(filters: Filters<Field>, ruleGroup: RuleGroup): ValidatedRuleGroup<Field>;
|
|
189
|
-
export declare function convertRuleGroupToFilters(ruleGroup?: RuleGroup): Filter[] | null;
|
|
190
|
-
export declare function convertFiltersToRuleGroup(matchType: "all" | "any", filters: Filter[]): RuleGroup;
|
|
191
182
|
export * from "./callouts";
|
|
192
183
|
export * from "./contacts";
|
|
193
184
|
export * from "./notices";
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
declare const dateUnits: readonly ["s", "m", "h", "d", "M", "y"];
|
|
2
|
+
declare type DateUnit = typeof dateUnits[number];
|
|
3
|
+
export declare function parseDate(value: string, now?: Date): [Date, DateUnit];
|
|
4
|
+
export declare function getMinDateUnit(units: DateUnit[]): DateUnit | undefined;
|
|
5
|
+
export declare function isValidDate(s: string): boolean;
|
|
6
|
+
export {};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { Filter, Filters, Rule, RuleGroup, ValidatedRule, ValidatedRuleGroup } from "../search";
|
|
2
|
+
export declare function isRuleGroup(ruleOrGroup: Rule | RuleGroup): ruleOrGroup is RuleGroup;
|
|
3
|
+
export declare function validateRule<Field extends string>(filters: Filters<Field>, rule: Rule): ValidatedRule<Field>;
|
|
4
|
+
export declare function validateRuleGroup<Field extends string>(filters: Filters<Field>, ruleGroup: RuleGroup): ValidatedRuleGroup<Field>;
|
|
5
|
+
export declare function convertRuleGroupToFilters(ruleGroup?: RuleGroup): Filter[] | null;
|
|
6
|
+
export declare function convertFiltersToRuleGroup(matchType: "all" | "any", filters: Filter[]): RuleGroup;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@beabee/beabee-common",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.3",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "./dist/cjs/index.js",
|
|
6
6
|
"module": "./dist/esm/index.js",
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
"dist/"
|
|
10
10
|
],
|
|
11
11
|
"scripts": {
|
|
12
|
-
"test": "
|
|
13
|
-
"build": "rimraf ./dist/ && tsc -p tsconfig.esm.json && tsc -p tsconfig.cjs.json && tsc -p tsconfig.types.json",
|
|
12
|
+
"test": "jest",
|
|
13
|
+
"build": "rimraf ./dist/ && tsc -p tsconfig.build.esm.json && tsc -p tsconfig.build.cjs.json && tsc -p tsconfig.build.types.json",
|
|
14
14
|
"prepublishOnly": "npm run build"
|
|
15
15
|
},
|
|
16
16
|
"repository": {
|
|
@@ -25,8 +25,10 @@
|
|
|
25
25
|
"homepage": "https://github.com/beabee-communityrm/beabee-common#readme",
|
|
26
26
|
"devDependencies": {
|
|
27
27
|
"@tsconfig/recommended": "^1.0.1",
|
|
28
|
+
"jest": "^29.3.0",
|
|
28
29
|
"prettier": "^2.7.1",
|
|
29
30
|
"rimraf": "^3.0.2",
|
|
31
|
+
"ts-jest": "^29.0.3",
|
|
30
32
|
"typescript": "^4.8.4"
|
|
31
33
|
},
|
|
32
34
|
"dependencies": {
|