@beabee/beabee-common 1.6.1 → 1.7.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.
@@ -34,8 +34,10 @@ exports.ruleOperators = [
34
34
  "is_empty",
35
35
  "is_not_empty",
36
36
  ];
37
- const equal = { args: 1 };
38
- const equalityOperators = { equal, not_equal: { args: 1 } };
37
+ const equalityOperators = {
38
+ equal: { args: 1 },
39
+ not_equal: { args: 1 },
40
+ };
39
41
  const numericOperators = {
40
42
  ...equalityOperators,
41
43
  between: { args: 2 },
@@ -49,6 +51,7 @@ const arrayOperators = {
49
51
  contains: { args: 1 },
50
52
  not_contains: { args: 1 },
51
53
  };
54
+ // Special operator can be applied across all fields if they are nullable
52
55
  exports.nullableOperators = {
53
56
  is_empty: { args: 0 },
54
57
  is_not_empty: { args: 0 },
@@ -64,11 +67,13 @@ exports.operatorsByType = {
64
67
  },
65
68
  date: numericOperators,
66
69
  number: numericOperators,
67
- boolean: { equal },
70
+ boolean: { equal: equalityOperators.equal },
68
71
  array: arrayOperators,
69
72
  enum: equalityOperators,
70
73
  contact: equalityOperators,
71
74
  };
75
+ // More general type to allow mapping while maintaining full type above
76
+ const operatorsByTypeMap = exports.operatorsByType;
72
77
  // *** Helper methods ***
73
78
  function isRuleGroup(ruleOrGroup) {
74
79
  return "condition" in ruleOrGroup;
@@ -79,17 +84,44 @@ function validateRule(filters, rule) {
79
84
  if (!filter) {
80
85
  return false; // Invalid field
81
86
  }
82
- const operator = exports.operatorsByType[filter.type][rule.operator];
83
- if (!operator) {
84
- return false; // Invalid operator
87
+ if (rule.operator in exports.nullableOperators) {
88
+ if (!filter.nullable && filter.type !== "text") {
89
+ return false; // Field cannot be empty (text can always be empty)
90
+ }
91
+ if (rule.value.length !== 0) {
92
+ return false; // Should have no args
93
+ }
94
+ }
95
+ else {
96
+ const operator = operatorsByTypeMap[filter.type][rule.operator];
97
+ if (!operator) {
98
+ return false; // Invalid operator
99
+ }
100
+ if (operator.args !== rule.value.length) {
101
+ return false; // Invalid number of args
102
+ }
103
+ }
104
+ const expectedType = filter.type === "boolean" || filter.type === "number"
105
+ ? filter.type
106
+ : "string";
107
+ if (rule.value.some((v) => typeof v !== expectedType)) {
108
+ return false; // Invalid value type
85
109
  }
86
- if (operator.args !== rule.value.length) {
87
- return false; // Invalid number of args
110
+ if (filter.type === "date" &&
111
+ rule.value.some((v) => isNaN(+new Date(v)))) {
112
+ return false; // Invalid date
88
113
  }
89
- return true;
114
+ return {
115
+ ...rule,
116
+ type: filter.type,
117
+ };
90
118
  }
91
119
  exports.validateRule = validateRule;
92
120
  function validateRuleGroup(filters, ruleGroup) {
121
+ const validatedRuleGroup = {
122
+ condition: ruleGroup.condition,
123
+ rules: [],
124
+ };
93
125
  for (const rule of ruleGroup.rules) {
94
126
  const valid = isRuleGroup(rule)
95
127
  ? validateRuleGroup(filters, rule)
@@ -97,8 +129,9 @@ function validateRuleGroup(filters, ruleGroup) {
97
129
  if (!valid) {
98
130
  return false;
99
131
  }
132
+ validatedRuleGroup.rules.push(valid);
100
133
  }
101
- return true;
134
+ return validatedRuleGroup;
102
135
  }
103
136
  exports.validateRuleGroup = validateRuleGroup;
104
137
  function convertRuleGroupToFilters(ruleGroup) {
@@ -17,8 +17,10 @@ export const ruleOperators = [
17
17
  "is_empty",
18
18
  "is_not_empty",
19
19
  ];
20
- const equal = { args: 1 };
21
- const equalityOperators = { equal, not_equal: { args: 1 } };
20
+ const equalityOperators = {
21
+ equal: { args: 1 },
22
+ not_equal: { args: 1 },
23
+ };
22
24
  const numericOperators = {
23
25
  ...equalityOperators,
24
26
  between: { args: 2 },
@@ -32,6 +34,7 @@ const arrayOperators = {
32
34
  contains: { args: 1 },
33
35
  not_contains: { args: 1 },
34
36
  };
37
+ // Special operator can be applied across all fields if they are nullable
35
38
  export const nullableOperators = {
36
39
  is_empty: { args: 0 },
37
40
  is_not_empty: { args: 0 },
@@ -47,11 +50,13 @@ export const operatorsByType = {
47
50
  },
48
51
  date: numericOperators,
49
52
  number: numericOperators,
50
- boolean: { equal },
53
+ boolean: { equal: equalityOperators.equal },
51
54
  array: arrayOperators,
52
55
  enum: equalityOperators,
53
56
  contact: equalityOperators,
54
57
  };
58
+ // More general type to allow mapping while maintaining full type above
59
+ const operatorsByTypeMap = operatorsByType;
55
60
  // *** Helper methods ***
56
61
  export function isRuleGroup(ruleOrGroup) {
57
62
  return "condition" in ruleOrGroup;
@@ -61,16 +66,43 @@ export function validateRule(filters, rule) {
61
66
  if (!filter) {
62
67
  return false; // Invalid field
63
68
  }
64
- const operator = operatorsByType[filter.type][rule.operator];
65
- if (!operator) {
66
- return false; // Invalid operator
69
+ if (rule.operator in nullableOperators) {
70
+ if (!filter.nullable && filter.type !== "text") {
71
+ return false; // Field cannot be empty (text can always be empty)
72
+ }
73
+ if (rule.value.length !== 0) {
74
+ return false; // Should have no args
75
+ }
76
+ }
77
+ else {
78
+ const operator = operatorsByTypeMap[filter.type][rule.operator];
79
+ if (!operator) {
80
+ return false; // Invalid operator
81
+ }
82
+ if (operator.args !== rule.value.length) {
83
+ return false; // Invalid number of args
84
+ }
85
+ }
86
+ const expectedType = filter.type === "boolean" || filter.type === "number"
87
+ ? filter.type
88
+ : "string";
89
+ if (rule.value.some((v) => typeof v !== expectedType)) {
90
+ return false; // Invalid value type
67
91
  }
68
- if (operator.args !== rule.value.length) {
69
- return false; // Invalid number of args
92
+ if (filter.type === "date" &&
93
+ rule.value.some((v) => isNaN(+new Date(v)))) {
94
+ return false; // Invalid date
70
95
  }
71
- return true;
96
+ return {
97
+ ...rule,
98
+ type: filter.type,
99
+ };
72
100
  }
73
101
  export function validateRuleGroup(filters, ruleGroup) {
102
+ const validatedRuleGroup = {
103
+ condition: ruleGroup.condition,
104
+ rules: [],
105
+ };
74
106
  for (const rule of ruleGroup.rules) {
75
107
  const valid = isRuleGroup(rule)
76
108
  ? validateRuleGroup(filters, rule)
@@ -78,8 +110,9 @@ export function validateRuleGroup(filters, ruleGroup) {
78
110
  if (!valid) {
79
111
  return false;
80
112
  }
113
+ validatedRuleGroup.rules.push(valid);
81
114
  }
82
- return true;
115
+ return validatedRuleGroup;
83
116
  }
84
117
  export function convertRuleGroupToFilters(ruleGroup) {
85
118
  if (!ruleGroup) {
@@ -1,14 +1,28 @@
1
1
  export declare const ruleOperators: readonly ["equal", "not_equal", "less", "less_or_equal", "greater", "greater_or_equal", "between", "not_between", "begins_with", "not_begins_with", "contains", "not_contains", "ends_with", "not_ends_with", "is_empty", "is_not_empty"];
2
2
  export declare type RuleOperator = typeof ruleOperators[number];
3
3
  export declare type RuleValue = string | number | boolean;
4
- export interface Rule<Field extends string> {
5
- field: Field;
4
+ export interface Rule {
5
+ field: string;
6
6
  operator: RuleOperator;
7
7
  value: RuleValue[];
8
8
  }
9
- export interface RuleGroup<Field extends string> {
9
+ export interface RuleGroup {
10
10
  condition: "AND" | "OR";
11
- rules: (RuleGroup<Field> | Rule<Field>)[];
11
+ rules: (RuleGroup | Rule)[];
12
+ }
13
+ interface BaseValidatedRule<T extends FilterType, F extends string, V> {
14
+ type: T;
15
+ field: F;
16
+ operator: keyof typeof operatorsByType[T];
17
+ value: V[];
18
+ }
19
+ declare type ValidatedNumberRule<Field extends string> = BaseValidatedRule<"number", Field, number>;
20
+ declare type ValidatedStringRule<Field extends string> = BaseValidatedRule<Exclude<FilterType, "number" | "boolean">, Field, string>;
21
+ declare type ValidatedBooleanRule<Field extends string> = BaseValidatedRule<"boolean", Field, boolean>;
22
+ export declare type ValidatedRule<Field extends string> = ValidatedNumberRule<Field> | ValidatedStringRule<Field> | ValidatedBooleanRule<Field>;
23
+ export interface ValidatedRuleGroup<Field extends string> {
24
+ condition: "AND" | "OR";
25
+ rules: (ValidatedRuleGroup<Field> | ValidatedRule<Field>)[];
12
26
  }
13
27
  export declare type FilterType = "text" | "date" | "number" | "boolean" | "array" | "enum" | "contact";
14
28
  export declare type FilterValue = RuleValue;
@@ -24,7 +38,115 @@ export declare const nullableOperators: {
24
38
  args: number;
25
39
  };
26
40
  };
27
- export declare const operatorsByType: Record<FilterType, Partial<Record<FilterOperator, FilterOperatorParams>>>;
41
+ export declare const operatorsByType: {
42
+ readonly text: {
43
+ readonly begins_with: {
44
+ readonly args: 1;
45
+ };
46
+ readonly ends_with: {
47
+ readonly args: 1;
48
+ };
49
+ readonly not_begins_with: {
50
+ readonly args: 1;
51
+ };
52
+ readonly not_ends_with: {
53
+ readonly args: 1;
54
+ };
55
+ readonly contains: {
56
+ args: number;
57
+ };
58
+ readonly not_contains: {
59
+ args: number;
60
+ };
61
+ readonly equal: {
62
+ args: number;
63
+ };
64
+ readonly not_equal: {
65
+ args: number;
66
+ };
67
+ };
68
+ readonly date: {
69
+ between: {
70
+ args: number;
71
+ };
72
+ not_between: {
73
+ args: number;
74
+ };
75
+ less: {
76
+ args: number;
77
+ };
78
+ greater: {
79
+ args: number;
80
+ };
81
+ less_or_equal: {
82
+ args: number;
83
+ };
84
+ greater_or_equal: {
85
+ args: number;
86
+ };
87
+ equal: {
88
+ args: number;
89
+ };
90
+ not_equal: {
91
+ args: number;
92
+ };
93
+ };
94
+ readonly number: {
95
+ between: {
96
+ args: number;
97
+ };
98
+ not_between: {
99
+ args: number;
100
+ };
101
+ less: {
102
+ args: number;
103
+ };
104
+ greater: {
105
+ args: number;
106
+ };
107
+ less_or_equal: {
108
+ args: number;
109
+ };
110
+ greater_or_equal: {
111
+ args: number;
112
+ };
113
+ equal: {
114
+ args: number;
115
+ };
116
+ not_equal: {
117
+ args: number;
118
+ };
119
+ };
120
+ readonly boolean: {
121
+ readonly equal: {
122
+ args: number;
123
+ };
124
+ };
125
+ readonly array: {
126
+ contains: {
127
+ args: number;
128
+ };
129
+ not_contains: {
130
+ args: number;
131
+ };
132
+ };
133
+ readonly enum: {
134
+ equal: {
135
+ args: number;
136
+ };
137
+ not_equal: {
138
+ args: number;
139
+ };
140
+ };
141
+ readonly contact: {
142
+ equal: {
143
+ args: number;
144
+ };
145
+ not_equal: {
146
+ args: number;
147
+ };
148
+ };
149
+ };
28
150
  interface BaseFilterArgs {
29
151
  type: FilterType;
30
152
  nullable?: boolean;
@@ -49,18 +171,18 @@ export interface Paginated<T> {
49
171
  count: number;
50
172
  total: number;
51
173
  }
52
- export interface PaginatedQuery<Field extends string> {
174
+ export interface PaginatedQuery {
53
175
  limit?: number;
54
176
  offset?: number;
55
177
  sort?: string;
56
178
  order?: "ASC" | "DESC";
57
- rules?: RuleGroup<Field>;
179
+ rules?: RuleGroup;
58
180
  }
59
- export declare function isRuleGroup<Field extends string>(ruleOrGroup: Rule<Field> | RuleGroup<Field>): ruleOrGroup is RuleGroup<Field>;
60
- export declare function validateRule<Field extends string>(filters: Filters<Field>, rule: Rule<string>): rule is Rule<Field>;
61
- export declare function validateRuleGroup<Field extends string>(filters: Filters<Field>, ruleGroup: RuleGroup<string>): ruleGroup is RuleGroup<Field>;
62
- export declare function convertRuleGroupToFilters(ruleGroup?: RuleGroup<string>): Filter[] | null;
63
- export declare function convertFiltersToRuleGroup(matchType: "all" | "any", filters: Filter[]): RuleGroup<string>;
181
+ export declare function isRuleGroup(ruleOrGroup: Rule | RuleGroup): ruleOrGroup is RuleGroup;
182
+ export declare function validateRule<Field extends string>(filters: Filters<Field>, rule: Rule): ValidatedRule<Field> | false;
183
+ export declare function validateRuleGroup<Field extends string>(filters: Filters<Field>, ruleGroup: RuleGroup): ValidatedRuleGroup<Field> | false;
184
+ export declare function convertRuleGroupToFilters(ruleGroup?: RuleGroup): Filter[] | null;
185
+ export declare function convertFiltersToRuleGroup(matchType: "all" | "any", filters: Filter[]): RuleGroup;
64
186
  export * from "./callouts";
65
187
  export * from "./contacts";
66
188
  export * from "./notices";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@beabee/beabee-common",
3
- "version": "1.6.1",
3
+ "version": "1.7.1",
4
4
  "description": "",
5
5
  "main": "./dist/cjs/index.js",
6
6
  "module": "./dist/esm/index.js",