@f-o-t/rules-engine 2.0.2 → 3.0.0
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/CHANGELOG.md +168 -0
- package/LICENSE.md +16 -4
- package/README.md +106 -23
- package/__tests__/builder.test.ts +363 -0
- package/__tests__/cache.test.ts +130 -0
- package/__tests__/config.test.ts +35 -0
- package/__tests__/engine.test.ts +1213 -0
- package/__tests__/evaluate.test.ts +339 -0
- package/__tests__/exports.test.ts +30 -0
- package/__tests__/filter-sort.test.ts +303 -0
- package/__tests__/integration.test.ts +419 -0
- package/__tests__/money-integration.test.ts +149 -0
- package/__tests__/validation.test.ts +862 -0
- package/biome.json +39 -0
- package/docs/MIGRATION-v3.md +118 -0
- package/fot.config.ts +5 -0
- package/package.json +31 -67
- package/src/analyzer/analysis.ts +401 -0
- package/src/builder/conditions.ts +321 -0
- package/src/builder/rule.ts +192 -0
- package/src/cache/cache.ts +135 -0
- package/src/cache/noop.ts +20 -0
- package/src/core/evaluate.ts +185 -0
- package/src/core/filter.ts +85 -0
- package/src/core/group.ts +103 -0
- package/src/core/sort.ts +90 -0
- package/src/engine/engine.ts +462 -0
- package/src/engine/hooks.ts +235 -0
- package/src/engine/state.ts +322 -0
- package/src/index.ts +303 -0
- package/src/optimizer/index-builder.ts +381 -0
- package/src/serialization/serializer.ts +408 -0
- package/src/simulation/simulator.ts +359 -0
- package/src/types/config.ts +184 -0
- package/src/types/consequence.ts +38 -0
- package/src/types/evaluation.ts +87 -0
- package/src/types/rule.ts +112 -0
- package/src/types/state.ts +116 -0
- package/src/utils/conditions.ts +108 -0
- package/src/utils/hash.ts +30 -0
- package/src/utils/id.ts +6 -0
- package/src/utils/time.ts +42 -0
- package/src/validation/conflicts.ts +440 -0
- package/src/validation/integrity.ts +473 -0
- package/src/validation/schema.ts +386 -0
- package/src/versioning/version-store.ts +337 -0
- package/tsconfig.json +29 -0
- package/dist/index.cjs +0 -3088
- package/dist/index.d.cts +0 -1173
- package/dist/index.d.ts +0 -1173
- package/dist/index.js +0 -3072
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createEvaluator,
|
|
3
|
+
type GroupEvaluationResult,
|
|
4
|
+
} from "@f-o-t/condition-evaluator";
|
|
5
|
+
import type {
|
|
6
|
+
AggregatedConsequence,
|
|
7
|
+
ConsequenceDefinitions,
|
|
8
|
+
DefaultConsequences,
|
|
9
|
+
} from "../types/consequence";
|
|
10
|
+
import type {
|
|
11
|
+
EvaluateConfig,
|
|
12
|
+
EvaluationContext,
|
|
13
|
+
RuleEvaluationResult,
|
|
14
|
+
} from "../types/evaluation";
|
|
15
|
+
import type { Rule } from "../types/rule";
|
|
16
|
+
import { measureTime } from "../utils/time";
|
|
17
|
+
|
|
18
|
+
export type EvaluateRuleOptions = {
|
|
19
|
+
readonly skipDisabled?: boolean;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const evaluateRule = <
|
|
23
|
+
TContext = unknown,
|
|
24
|
+
TConsequences extends ConsequenceDefinitions = DefaultConsequences,
|
|
25
|
+
>(
|
|
26
|
+
rule: Rule<TContext, TConsequences>,
|
|
27
|
+
context: EvaluationContext<TContext>,
|
|
28
|
+
evaluator: ReturnType<typeof createEvaluator>,
|
|
29
|
+
options: EvaluateRuleOptions = {},
|
|
30
|
+
): RuleEvaluationResult<TContext, TConsequences> => {
|
|
31
|
+
if (options.skipDisabled && !rule.enabled) {
|
|
32
|
+
return {
|
|
33
|
+
ruleId: rule.id,
|
|
34
|
+
ruleName: rule.name,
|
|
35
|
+
matched: false,
|
|
36
|
+
conditionResult: createEmptyGroupResult(rule.conditions.id),
|
|
37
|
+
consequences: [],
|
|
38
|
+
evaluationTimeMs: 0,
|
|
39
|
+
skipped: true,
|
|
40
|
+
skipReason: "Rule is disabled",
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const { result: conditionResult, durationMs } = measureTime(() => {
|
|
45
|
+
try {
|
|
46
|
+
const evalContext = {
|
|
47
|
+
data: context.data as Record<string, unknown>,
|
|
48
|
+
metadata: context.metadata as Record<string, unknown> | undefined,
|
|
49
|
+
};
|
|
50
|
+
// Use the provided evaluator instead of hardcoded evaluateConditionGroup
|
|
51
|
+
return evaluator.evaluateConditionGroup(rule.conditions, evalContext);
|
|
52
|
+
} catch (error) {
|
|
53
|
+
return {
|
|
54
|
+
error,
|
|
55
|
+
result: createEmptyGroupResult(rule.conditions.id),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
if ("error" in conditionResult) {
|
|
61
|
+
return {
|
|
62
|
+
ruleId: rule.id,
|
|
63
|
+
ruleName: rule.name,
|
|
64
|
+
matched: false,
|
|
65
|
+
conditionResult: conditionResult.result,
|
|
66
|
+
consequences: [],
|
|
67
|
+
evaluationTimeMs: durationMs,
|
|
68
|
+
skipped: false,
|
|
69
|
+
error:
|
|
70
|
+
conditionResult.error instanceof Error
|
|
71
|
+
? conditionResult.error
|
|
72
|
+
: new Error(String(conditionResult.error)),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const matched = conditionResult.passed;
|
|
77
|
+
|
|
78
|
+
const consequences: AggregatedConsequence<TConsequences>[] = matched
|
|
79
|
+
? rule.consequences.map((consequence) => ({
|
|
80
|
+
type: consequence.type,
|
|
81
|
+
payload: consequence.payload,
|
|
82
|
+
ruleId: rule.id,
|
|
83
|
+
ruleName: rule.name,
|
|
84
|
+
priority: rule.priority,
|
|
85
|
+
}))
|
|
86
|
+
: [];
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
ruleId: rule.id,
|
|
90
|
+
ruleName: rule.name,
|
|
91
|
+
matched,
|
|
92
|
+
conditionResult,
|
|
93
|
+
consequences,
|
|
94
|
+
evaluationTimeMs: durationMs,
|
|
95
|
+
skipped: false,
|
|
96
|
+
};
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const createEmptyGroupResult = (groupId: string): GroupEvaluationResult => ({
|
|
100
|
+
groupId,
|
|
101
|
+
operator: "AND",
|
|
102
|
+
passed: false,
|
|
103
|
+
results: [],
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
export type EvaluateRulesOptions<
|
|
107
|
+
TContext = unknown,
|
|
108
|
+
TConsequences extends ConsequenceDefinitions = DefaultConsequences,
|
|
109
|
+
> = {
|
|
110
|
+
readonly config?: Partial<EvaluateConfig>;
|
|
111
|
+
readonly onRuleEvaluated?: (
|
|
112
|
+
result: RuleEvaluationResult<TContext, TConsequences>,
|
|
113
|
+
) => void;
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
export type EvaluateRulesResult<
|
|
117
|
+
TContext = unknown,
|
|
118
|
+
TConsequences extends ConsequenceDefinitions = DefaultConsequences,
|
|
119
|
+
> = {
|
|
120
|
+
readonly results: ReadonlyArray<
|
|
121
|
+
RuleEvaluationResult<TContext, TConsequences>
|
|
122
|
+
>;
|
|
123
|
+
readonly matchedRules: ReadonlyArray<Rule<TContext, TConsequences>>;
|
|
124
|
+
readonly consequences: ReadonlyArray<AggregatedConsequence<TConsequences>>;
|
|
125
|
+
readonly stoppedEarly: boolean;
|
|
126
|
+
readonly stoppedByRuleId?: string;
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
export const evaluateRules = <
|
|
130
|
+
TContext = unknown,
|
|
131
|
+
TConsequences extends ConsequenceDefinitions = DefaultConsequences,
|
|
132
|
+
>(
|
|
133
|
+
rules: ReadonlyArray<Rule<TContext, TConsequences>>,
|
|
134
|
+
context: EvaluationContext<TContext>,
|
|
135
|
+
evaluator: ReturnType<typeof createEvaluator>,
|
|
136
|
+
options: EvaluateRulesOptions<TContext, TConsequences> = {},
|
|
137
|
+
): EvaluateRulesResult<TContext, TConsequences> => {
|
|
138
|
+
const config: EvaluateConfig = {
|
|
139
|
+
conflictResolution: options.config?.conflictResolution ?? "priority",
|
|
140
|
+
continueOnError: options.config?.continueOnError ?? true,
|
|
141
|
+
collectAllConsequences: options.config?.collectAllConsequences ?? true,
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const results: RuleEvaluationResult<TContext, TConsequences>[] = [];
|
|
145
|
+
const matchedRules: Rule<TContext, TConsequences>[] = [];
|
|
146
|
+
const consequences: AggregatedConsequence<TConsequences>[] = [];
|
|
147
|
+
let stoppedEarly = false;
|
|
148
|
+
let stoppedByRuleId: string | undefined;
|
|
149
|
+
|
|
150
|
+
for (const rule of rules) {
|
|
151
|
+
const result = evaluateRule(rule, context, evaluator, { skipDisabled: true });
|
|
152
|
+
results.push(result);
|
|
153
|
+
|
|
154
|
+
options.onRuleEvaluated?.(result);
|
|
155
|
+
|
|
156
|
+
if (result.error && !config.continueOnError) {
|
|
157
|
+
break;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (result.matched) {
|
|
161
|
+
matchedRules.push(rule);
|
|
162
|
+
consequences.push(...result.consequences);
|
|
163
|
+
|
|
164
|
+
if (rule.stopOnMatch) {
|
|
165
|
+
stoppedEarly = true;
|
|
166
|
+
stoppedByRuleId = rule.id;
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (config.conflictResolution === "first-match") {
|
|
171
|
+
stoppedEarly = true;
|
|
172
|
+
stoppedByRuleId = rule.id;
|
|
173
|
+
break;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return {
|
|
179
|
+
results,
|
|
180
|
+
matchedRules,
|
|
181
|
+
consequences,
|
|
182
|
+
stoppedEarly,
|
|
183
|
+
stoppedByRuleId,
|
|
184
|
+
};
|
|
185
|
+
};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ConsequenceDefinitions,
|
|
3
|
+
DefaultConsequences,
|
|
4
|
+
} from "../types/consequence";
|
|
5
|
+
import type { Rule, RuleFilters } from "../types/rule";
|
|
6
|
+
|
|
7
|
+
export const filterRules = <
|
|
8
|
+
TContext = unknown,
|
|
9
|
+
TConsequences extends ConsequenceDefinitions = DefaultConsequences,
|
|
10
|
+
>(
|
|
11
|
+
filters: RuleFilters,
|
|
12
|
+
) => {
|
|
13
|
+
return (
|
|
14
|
+
rules: ReadonlyArray<Rule<TContext, TConsequences>>,
|
|
15
|
+
): ReadonlyArray<Rule<TContext, TConsequences>> => {
|
|
16
|
+
return rules.filter((rule) => {
|
|
17
|
+
if (
|
|
18
|
+
filters.enabled !== undefined &&
|
|
19
|
+
rule.enabled !== filters.enabled
|
|
20
|
+
) {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (filters.tags && filters.tags.length > 0) {
|
|
25
|
+
const hasMatchingTag = filters.tags.some((tag) =>
|
|
26
|
+
rule.tags.includes(tag),
|
|
27
|
+
);
|
|
28
|
+
if (!hasMatchingTag) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (
|
|
34
|
+
filters.category !== undefined &&
|
|
35
|
+
rule.category !== filters.category
|
|
36
|
+
) {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (filters.ids && filters.ids.length > 0) {
|
|
41
|
+
if (!filters.ids.includes(rule.id)) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return true;
|
|
47
|
+
});
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export const filterByEnabled = <
|
|
52
|
+
TContext = unknown,
|
|
53
|
+
TConsequences extends ConsequenceDefinitions = DefaultConsequences,
|
|
54
|
+
>(
|
|
55
|
+
enabled: boolean,
|
|
56
|
+
) => {
|
|
57
|
+
return filterRules<TContext, TConsequences>({ enabled });
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export const filterByTags = <
|
|
61
|
+
TContext = unknown,
|
|
62
|
+
TConsequences extends ConsequenceDefinitions = DefaultConsequences,
|
|
63
|
+
>(
|
|
64
|
+
tags: ReadonlyArray<string>,
|
|
65
|
+
) => {
|
|
66
|
+
return filterRules<TContext, TConsequences>({ tags });
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export const filterByCategory = <
|
|
70
|
+
TContext = unknown,
|
|
71
|
+
TConsequences extends ConsequenceDefinitions = DefaultConsequences,
|
|
72
|
+
>(
|
|
73
|
+
category: string,
|
|
74
|
+
) => {
|
|
75
|
+
return filterRules<TContext, TConsequences>({ category });
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export const filterByIds = <
|
|
79
|
+
TContext = unknown,
|
|
80
|
+
TConsequences extends ConsequenceDefinitions = DefaultConsequences,
|
|
81
|
+
>(
|
|
82
|
+
ids: ReadonlyArray<string>,
|
|
83
|
+
) => {
|
|
84
|
+
return filterRules<TContext, TConsequences>({ ids });
|
|
85
|
+
};
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ConsequenceDefinitions,
|
|
3
|
+
DefaultConsequences,
|
|
4
|
+
} from "../types/consequence";
|
|
5
|
+
import type { Rule } from "../types/rule";
|
|
6
|
+
|
|
7
|
+
export type GroupByField = "category" | "priority" | "enabled";
|
|
8
|
+
|
|
9
|
+
export type GroupedRules<
|
|
10
|
+
TContext = unknown,
|
|
11
|
+
TConsequences extends ConsequenceDefinitions = DefaultConsequences,
|
|
12
|
+
> = ReadonlyMap<
|
|
13
|
+
string | number | boolean,
|
|
14
|
+
ReadonlyArray<Rule<TContext, TConsequences>>
|
|
15
|
+
>;
|
|
16
|
+
|
|
17
|
+
export const groupRules = <
|
|
18
|
+
TContext = unknown,
|
|
19
|
+
TConsequences extends ConsequenceDefinitions = DefaultConsequences,
|
|
20
|
+
>(
|
|
21
|
+
field: GroupByField,
|
|
22
|
+
) => {
|
|
23
|
+
return (
|
|
24
|
+
rules: ReadonlyArray<Rule<TContext, TConsequences>>,
|
|
25
|
+
): GroupedRules<TContext, TConsequences> => {
|
|
26
|
+
const groups = new Map<
|
|
27
|
+
string | number | boolean,
|
|
28
|
+
Rule<TContext, TConsequences>[]
|
|
29
|
+
>();
|
|
30
|
+
|
|
31
|
+
for (const rule of rules) {
|
|
32
|
+
let key: string | number | boolean;
|
|
33
|
+
|
|
34
|
+
switch (field) {
|
|
35
|
+
case "category":
|
|
36
|
+
key = rule.category ?? "uncategorized";
|
|
37
|
+
break;
|
|
38
|
+
case "priority":
|
|
39
|
+
key = rule.priority;
|
|
40
|
+
break;
|
|
41
|
+
case "enabled":
|
|
42
|
+
key = rule.enabled;
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const existing = groups.get(key);
|
|
47
|
+
if (existing) {
|
|
48
|
+
existing.push(rule);
|
|
49
|
+
} else {
|
|
50
|
+
groups.set(key, [rule]);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return groups;
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export const groupByCategory = <
|
|
59
|
+
TContext = unknown,
|
|
60
|
+
TConsequences extends ConsequenceDefinitions = DefaultConsequences,
|
|
61
|
+
>() => {
|
|
62
|
+
return groupRules<TContext, TConsequences>("category");
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export const groupByPriority = <
|
|
66
|
+
TContext = unknown,
|
|
67
|
+
TConsequences extends ConsequenceDefinitions = DefaultConsequences,
|
|
68
|
+
>() => {
|
|
69
|
+
return groupRules<TContext, TConsequences>("priority");
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export const groupByEnabled = <
|
|
73
|
+
TContext = unknown,
|
|
74
|
+
TConsequences extends ConsequenceDefinitions = DefaultConsequences,
|
|
75
|
+
>() => {
|
|
76
|
+
return groupRules<TContext, TConsequences>("enabled");
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export const groupByCustom = <
|
|
80
|
+
TContext = unknown,
|
|
81
|
+
TConsequences extends ConsequenceDefinitions = DefaultConsequences,
|
|
82
|
+
TKey extends string | number | boolean = string,
|
|
83
|
+
>(
|
|
84
|
+
keyFn: (rule: Rule<TContext, TConsequences>) => TKey,
|
|
85
|
+
) => {
|
|
86
|
+
return (
|
|
87
|
+
rules: ReadonlyArray<Rule<TContext, TConsequences>>,
|
|
88
|
+
): ReadonlyMap<TKey, ReadonlyArray<Rule<TContext, TConsequences>>> => {
|
|
89
|
+
const groups = new Map<TKey, Rule<TContext, TConsequences>[]>();
|
|
90
|
+
|
|
91
|
+
for (const rule of rules) {
|
|
92
|
+
const key = keyFn(rule);
|
|
93
|
+
const existing = groups.get(key);
|
|
94
|
+
if (existing) {
|
|
95
|
+
existing.push(rule);
|
|
96
|
+
} else {
|
|
97
|
+
groups.set(key, [rule]);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return groups;
|
|
102
|
+
};
|
|
103
|
+
};
|
package/src/core/sort.ts
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ConsequenceDefinitions,
|
|
3
|
+
DefaultConsequences,
|
|
4
|
+
} from "../types/consequence";
|
|
5
|
+
import type { Rule } from "../types/rule";
|
|
6
|
+
|
|
7
|
+
export type SortField = "priority" | "name" | "createdAt" | "updatedAt";
|
|
8
|
+
export type SortDirection = "asc" | "desc";
|
|
9
|
+
|
|
10
|
+
export type SortOptions = {
|
|
11
|
+
readonly field: SortField;
|
|
12
|
+
readonly direction?: SortDirection;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const sortRules = <
|
|
16
|
+
TContext = unknown,
|
|
17
|
+
TConsequences extends ConsequenceDefinitions = DefaultConsequences,
|
|
18
|
+
>(
|
|
19
|
+
options: SortField | SortOptions,
|
|
20
|
+
) => {
|
|
21
|
+
const normalizedOptions: SortOptions =
|
|
22
|
+
typeof options === "string"
|
|
23
|
+
? { field: options, direction: "desc" }
|
|
24
|
+
: options;
|
|
25
|
+
|
|
26
|
+
const { field, direction = "desc" } = normalizedOptions;
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
rules: ReadonlyArray<Rule<TContext, TConsequences>>,
|
|
30
|
+
): ReadonlyArray<Rule<TContext, TConsequences>> => {
|
|
31
|
+
const sorted = [...rules].sort((a, b) => {
|
|
32
|
+
let comparison = 0;
|
|
33
|
+
|
|
34
|
+
switch (field) {
|
|
35
|
+
case "priority":
|
|
36
|
+
comparison = a.priority - b.priority;
|
|
37
|
+
break;
|
|
38
|
+
case "name":
|
|
39
|
+
comparison = a.name.localeCompare(b.name);
|
|
40
|
+
break;
|
|
41
|
+
case "createdAt":
|
|
42
|
+
comparison = a.createdAt.getTime() - b.createdAt.getTime();
|
|
43
|
+
break;
|
|
44
|
+
case "updatedAt":
|
|
45
|
+
comparison = a.updatedAt.getTime() - b.updatedAt.getTime();
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return direction === "desc" ? -comparison : comparison;
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
return sorted;
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export const sortByPriority = <
|
|
57
|
+
TContext = unknown,
|
|
58
|
+
TConsequences extends ConsequenceDefinitions = DefaultConsequences,
|
|
59
|
+
>(
|
|
60
|
+
direction: SortDirection = "desc",
|
|
61
|
+
) => {
|
|
62
|
+
return sortRules<TContext, TConsequences>({ field: "priority", direction });
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export const sortByName = <
|
|
66
|
+
TContext = unknown,
|
|
67
|
+
TConsequences extends ConsequenceDefinitions = DefaultConsequences,
|
|
68
|
+
>(
|
|
69
|
+
direction: SortDirection = "asc",
|
|
70
|
+
) => {
|
|
71
|
+
return sortRules<TContext, TConsequences>({ field: "name", direction });
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export const sortByCreatedAt = <
|
|
75
|
+
TContext = unknown,
|
|
76
|
+
TConsequences extends ConsequenceDefinitions = DefaultConsequences,
|
|
77
|
+
>(
|
|
78
|
+
direction: SortDirection = "desc",
|
|
79
|
+
) => {
|
|
80
|
+
return sortRules<TContext, TConsequences>({ field: "createdAt", direction });
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
export const sortByUpdatedAt = <
|
|
84
|
+
TContext = unknown,
|
|
85
|
+
TConsequences extends ConsequenceDefinitions = DefaultConsequences,
|
|
86
|
+
>(
|
|
87
|
+
direction: SortDirection = "desc",
|
|
88
|
+
) => {
|
|
89
|
+
return sortRules<TContext, TConsequences>({ field: "updatedAt", direction });
|
|
90
|
+
};
|