@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.
@@ -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);
@@ -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.convertFiltersToRuleGroup = exports.convertRuleGroupToFilters = exports.validateRuleGroup = exports.validateRule = exports.InvalidRule = exports.isRuleGroup = exports.operatorsByType = exports.nullableOperators = exports.ruleOperators = void 0;
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
- const operatorsByTypeMap = exports.operatorsByType;
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;
@@ -0,0 +1,8 @@
1
+ export default class InvalidRule extends Error {
2
+ constructor(rule, message) {
3
+ super();
4
+ this.rule = rule;
5
+ this.message = message;
6
+ Object.setPrototypeOf(this, InvalidRule.prototype);
7
+ }
8
+ }
@@ -0,0 +1,2 @@
1
+ import InvalidRule from "./InvalidRule";
2
+ export { InvalidRule };
package/dist/esm/index.js CHANGED
@@ -1,2 +1,5 @@
1
1
  export * from "./search";
2
2
  export * from "./data";
3
+ export * from "./error";
4
+ export * from "./utils/date";
5
+ export * from "./utils/rules";
@@ -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
+ }
@@ -0,0 +1,6 @@
1
+ import { Rule } from "../search";
2
+ export default class InvalidRule extends Error {
3
+ readonly rule: Rule;
4
+ readonly message: string;
5
+ constructor(rule: Rule, message: string);
6
+ }
@@ -0,0 +1,2 @@
1
+ import InvalidRule from "./InvalidRule";
2
+ export { InvalidRule };
@@ -1,2 +1,5 @@
1
1
  export * from "./search";
2
2
  export * from "./data";
3
+ export * from "./error";
4
+ export * from "./utils/date";
5
+ export * from "./utils/rules";
@@ -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
- interface BaseFilterArgs {
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.1",
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": "echo \"Error: no test specified\" && exit 1",
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": {