@mgarlik/json-filter 0.1.0 → 1.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/dist/filterDocuments.d.ts +1 -1
- package/dist/filterDocuments.js +1 -1
- package/dist/index.d.ts +5 -5
- package/dist/index.js +4 -4
- package/dist/matchesFilter.d.ts +3 -24
- package/dist/matchesFilter.js +195 -227
- package/dist/typecheck.js +1 -1
- package/dist/types.d.ts +35 -8
- package/package.json +1 -1
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* - Pokud je `documents` jeden dokument, vrati dokument nebo `null`.
|
|
6
6
|
* - `null`/`undefined` na vstupu vraci beze zmeny.
|
|
7
7
|
*/
|
|
8
|
-
import type { contextType, filterType, targetType } from "./types";
|
|
8
|
+
import type { contextType, filterType, targetType } from "./types.js";
|
|
9
9
|
declare function filterDocuments<TTarget extends targetType, TContext extends contextType = contextType>(documents: TTarget[], filter?: filterType<TTarget, TContext> | boolean | null, context?: TContext): TTarget[];
|
|
10
10
|
declare function filterDocuments<TTarget extends targetType, TContext extends contextType = contextType>(documents: TTarget | null | undefined, filter?: filterType<TTarget, TContext> | boolean | null, context?: TContext): TTarget | null | undefined;
|
|
11
11
|
export { filterDocuments };
|
package/dist/filterDocuments.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* - Pokud je `documents` jeden dokument, vrati dokument nebo `null`.
|
|
6
6
|
* - `null`/`undefined` na vstupu vraci beze zmeny.
|
|
7
7
|
*/
|
|
8
|
-
import { matchesFilter } from "./matchesFilter";
|
|
8
|
+
import { matchesFilter } from "./matchesFilter.js";
|
|
9
9
|
function filterDocuments(documents, filter, context = {}) {
|
|
10
10
|
if (documents == null)
|
|
11
11
|
return documents;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { matchesFilter as matchJson, defineFilter as defineJsonFilter, filterArray as filterJsonArray, filterObject as filterJsonObjectMap, evaluateValue as evaluateJsonExpression, } from "./matchesFilter";
|
|
2
|
-
export { filterDocuments as filterJsonDocuments, } from "./filterDocuments";
|
|
3
|
-
export { getValueByDotPath as getJsonPathValue, } from "./dotPath";
|
|
4
|
-
export { default as transformJsonDocument, } from "./recalculateWithDocument";
|
|
5
|
-
export type { scalarType, scalarType as JsonScalar, contextType, contextType as JsonFilterContext, targetType, targetType as JsonDocument, conditionType, conditionType as JsonCondition, conditionObjectType, conditionObjectType as JsonConditionObject, filterType, filterType as JsonFilter, documentsArrayType, documentsArrayType as JsonDocumentsArray, documentsMapType, documentsMapType as JsonDocumentsMap, valueExpressionType, valueExpressionType as JsonExpression, } from "./types";
|
|
1
|
+
export { matchesFilter as matchJson, defineFilter as defineJsonFilter, filterArray as filterJsonArray, filterObject as filterJsonObjectMap, evaluateValue as evaluateJsonExpression, } from "./matchesFilter.js";
|
|
2
|
+
export { filterDocuments as filterJsonDocuments, } from "./filterDocuments.js";
|
|
3
|
+
export { getValueByDotPath as getJsonPathValue, } from "./dotPath.js";
|
|
4
|
+
export { default as transformJsonDocument, } from "./recalculateWithDocument.js";
|
|
5
|
+
export type { scalarType, scalarType as JsonScalar, contextType, contextType as JsonFilterContext, targetType, targetType as JsonDocument, conditionType, conditionType as JsonCondition, conditionObjectType, conditionObjectType as JsonConditionObject, filterType, filterType as JsonFilter, documentsArrayType, documentsArrayType as JsonDocumentsArray, documentsMapType, documentsMapType as JsonDocumentsMap, valueExpressionType, valueExpressionType as JsonExpression, } from "./types.js";
|
package/dist/index.js
CHANGED
|
@@ -8,13 +8,13 @@ filterArray as filterJsonArray,
|
|
|
8
8
|
// filterObject,
|
|
9
9
|
filterObject as filterJsonObjectMap,
|
|
10
10
|
// evaluateValue,
|
|
11
|
-
evaluateValue as evaluateJsonExpression, } from "./matchesFilter";
|
|
11
|
+
evaluateValue as evaluateJsonExpression, } from "./matchesFilter.js";
|
|
12
12
|
export {
|
|
13
13
|
// filterDocuments,
|
|
14
|
-
filterDocuments as filterJsonDocuments, } from "./filterDocuments";
|
|
14
|
+
filterDocuments as filterJsonDocuments, } from "./filterDocuments.js";
|
|
15
15
|
export {
|
|
16
16
|
// getValueByDotPath,
|
|
17
|
-
getValueByDotPath as getJsonPathValue, } from "./dotPath";
|
|
17
|
+
getValueByDotPath as getJsonPathValue, } from "./dotPath.js";
|
|
18
18
|
export {
|
|
19
19
|
// default as recalculateObjectWithDocument,
|
|
20
|
-
default as transformJsonDocument, } from "./recalculateWithDocument";
|
|
20
|
+
default as transformJsonDocument, } from "./recalculateWithDocument.js";
|
package/dist/matchesFilter.d.ts
CHANGED
|
@@ -1,28 +1,7 @@
|
|
|
1
|
-
import type { contextType, documentsArrayType, documentsMapType, filterType, targetType, valueExpressionType } from "./types";
|
|
2
|
-
|
|
3
|
-
* Vyhodnoti, jestli dokument odpovida zadanemu filtru.
|
|
4
|
-
*
|
|
5
|
-
* @param target Dokument, nad kterym se filtr vyhodnocuje.
|
|
6
|
-
* @param filter Filtr nebo boolean hodnota. `null`/`undefined` znamena "vse projde".
|
|
7
|
-
* @param context Volitelny kontext pro reference jako `$ctx.*`.
|
|
8
|
-
* @returns `true`, pokud dokument splnuje filtr.
|
|
9
|
-
*/
|
|
10
|
-
export declare function matchesFilter<TTarget extends targetType, TContext extends contextType = contextType>(target: TTarget, filter: filterType<TTarget, TContext> | null | undefined | boolean, context?: TContext): boolean;
|
|
11
|
-
/**
|
|
12
|
-
* Pomocna funkce pro definici filtru se zachovanim plneho typovani.
|
|
13
|
-
*
|
|
14
|
-
* Hodí se hlavne kdyz filtr ulozis do promenne pred predanim do `matchesFilter`,
|
|
15
|
-
* aby VS Code spravne naseptaval cesty (`$this.*`, `$ctx.*`) a operatory.
|
|
16
|
-
*/
|
|
1
|
+
import type { contextType, documentsArrayType, documentsMapType, filterType, targetType, valueExpressionType } from "./types.js";
|
|
2
|
+
export declare function evaluateValue(expr: valueExpressionType, context?: contextType, target?: targetType): unknown;
|
|
17
3
|
export declare function defineFilter<TTarget extends targetType, TContext extends contextType = contextType>(filter: filterType<TTarget, TContext>): filterType<TTarget, TContext>;
|
|
18
|
-
|
|
19
|
-
* Vyhodnoti hodnotovy vyraz (napr. `$plus`, `$concat`, `$val`) v kontextu dat.
|
|
20
|
-
*
|
|
21
|
-
* @param expr Vyraz k vyhodnoceni.
|
|
22
|
-
* @param context Kontext pro reference jako `$ctx.*`.
|
|
23
|
-
* @returns Vysledna hodnota vyrazu.
|
|
24
|
-
*/
|
|
25
|
-
export declare function evaluateValue(expr: valueExpressionType, context?: contextType): unknown;
|
|
4
|
+
export declare function matchesFilter<TTarget extends targetType, TContext extends contextType = contextType>(target: TTarget, filter: filterType<TTarget, TContext> | null | undefined | boolean, context?: TContext): boolean;
|
|
26
5
|
/**
|
|
27
6
|
* Vyfiltruje pole JSON dokumentu podle filtru.
|
|
28
7
|
*/
|
package/dist/matchesFilter.js
CHANGED
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Resolves a dot-notation path on an object.
|
|
3
|
-
* "brand.name" -> obj.brand.name
|
|
4
|
-
*/
|
|
5
1
|
function resolvePath(obj, path) {
|
|
6
2
|
return path.split(".").reduce((cur, key) => {
|
|
7
3
|
if (cur == null || typeof cur !== "object")
|
|
@@ -9,130 +5,76 @@ function resolvePath(obj, path) {
|
|
|
9
5
|
return cur[key];
|
|
10
6
|
}, obj);
|
|
11
7
|
}
|
|
12
|
-
/**
|
|
13
|
-
* Resolves a context reference like "$profile.type"
|
|
14
|
-
* from the provided context object.
|
|
15
|
-
*/
|
|
16
8
|
function resolveContextRef(ref, context) {
|
|
17
9
|
if (typeof ref !== "string" || !ref.startsWith("$"))
|
|
18
10
|
return ref;
|
|
19
|
-
if (ref.startsWith("$ctx."))
|
|
11
|
+
if (ref.startsWith("$ctx."))
|
|
20
12
|
return resolvePath(context, ref.slice(5));
|
|
21
|
-
|
|
22
|
-
const path = ref.slice(1);
|
|
23
|
-
return resolvePath(context, path);
|
|
13
|
+
return resolvePath(context, ref.slice(1));
|
|
24
14
|
}
|
|
25
15
|
function resolveTargetRef(ref, target) {
|
|
26
16
|
if (typeof ref !== "string" || !ref.startsWith("$this."))
|
|
27
17
|
return ref;
|
|
28
18
|
return resolvePath(target, ref.slice(6));
|
|
29
19
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
return resolveTargetRef(val, target);
|
|
36
|
-
}
|
|
37
|
-
if (typeof val === "string" && val.startsWith("$")) {
|
|
38
|
-
return resolveContextRef(val, context);
|
|
20
|
+
function compareResolvedValues(fieldVal, resolved) {
|
|
21
|
+
if (Array.isArray(fieldVal)) {
|
|
22
|
+
if (Array.isArray(resolved))
|
|
23
|
+
return resolved.some((item) => fieldVal.includes(item));
|
|
24
|
+
return fieldVal.includes(resolved);
|
|
39
25
|
}
|
|
40
|
-
|
|
26
|
+
if (Array.isArray(resolved))
|
|
27
|
+
return resolved.includes(fieldVal);
|
|
28
|
+
return fieldVal === resolved;
|
|
41
29
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
* @param filter Filtr nebo boolean hodnota. `null`/`undefined` znamena "vse projde".
|
|
47
|
-
* @param context Volitelny kontext pro reference jako `$ctx.*`.
|
|
48
|
-
* @returns `true`, pokud dokument splnuje filtr.
|
|
49
|
-
*/
|
|
50
|
-
export function matchesFilter(target, filter, context = {}) {
|
|
51
|
-
if (filter === null || filter === undefined)
|
|
52
|
-
return true;
|
|
53
|
-
if (typeof filter !== "object")
|
|
54
|
-
return Boolean(filter);
|
|
55
|
-
const keys = Object.keys(filter);
|
|
56
|
-
for (const key of keys) {
|
|
57
|
-
const val = filter[key];
|
|
58
|
-
if (key === "$and") {
|
|
59
|
-
if (!evalAnd(target, val, context))
|
|
60
|
-
return false;
|
|
61
|
-
continue;
|
|
62
|
-
}
|
|
63
|
-
if (key === "$nand") {
|
|
64
|
-
if (evalAnd(target, val, context))
|
|
65
|
-
return false;
|
|
66
|
-
continue;
|
|
67
|
-
}
|
|
68
|
-
if (key === "$or") {
|
|
69
|
-
if (!evalOr(target, val, context))
|
|
70
|
-
return false;
|
|
71
|
-
continue;
|
|
72
|
-
}
|
|
73
|
-
if (key === "$nor") {
|
|
74
|
-
if (evalOr(target, val, context))
|
|
75
|
-
return false;
|
|
76
|
-
continue;
|
|
77
|
-
}
|
|
78
|
-
if (key === "$not") {
|
|
79
|
-
if (matchesFilter(target, val, context))
|
|
80
|
-
return false;
|
|
81
|
-
continue;
|
|
82
|
-
}
|
|
83
|
-
// Value reference as field: "$ctx.profile.type" or legacy "$profile.type"
|
|
84
|
-
if (key.startsWith("$ctx.") || (key.startsWith("$") && !key.startsWith("$this."))) {
|
|
85
|
-
const fieldVal = resolveContextRef(key, context);
|
|
86
|
-
if (!evalCondition(fieldVal, val, target, context))
|
|
87
|
-
return false;
|
|
88
|
-
continue;
|
|
89
|
-
}
|
|
90
|
-
if (key.startsWith("$this.")) {
|
|
91
|
-
const fieldVal = resolveTargetRef(key, target);
|
|
92
|
-
if (!evalCondition(fieldVal, val, target, context))
|
|
93
|
-
return false;
|
|
94
|
-
continue;
|
|
95
|
-
}
|
|
96
|
-
// Optional field (suffix ?)
|
|
97
|
-
const optional = key.endsWith("?");
|
|
98
|
-
const cleanKey = optional ? key.slice(0, -1) : key;
|
|
99
|
-
const fieldVal = resolvePath(target, cleanKey);
|
|
100
|
-
if (optional && fieldVal === undefined)
|
|
101
|
-
continue;
|
|
102
|
-
if (!evalCondition(fieldVal, val, target, context))
|
|
103
|
-
return false;
|
|
104
|
-
}
|
|
105
|
-
return true;
|
|
30
|
+
function buildSourceKey(source) {
|
|
31
|
+
if (typeof source !== "object" || source === null)
|
|
32
|
+
return String(source);
|
|
33
|
+
return JSON.stringify(source);
|
|
106
34
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
*
|
|
110
|
-
* Hodí se hlavne kdyz filtr ulozis do promenne pred predanim do `matchesFilter`,
|
|
111
|
-
* aby VS Code spravne naseptaval cesty (`$this.*`, `$ctx.*`) a operatory.
|
|
112
|
-
*/
|
|
113
|
-
export function defineFilter(filter) {
|
|
114
|
-
return filter;
|
|
35
|
+
function getCurrentDate() {
|
|
36
|
+
return new Date().toISOString().slice(0, 10);
|
|
115
37
|
}
|
|
116
|
-
function
|
|
117
|
-
return
|
|
38
|
+
function getCurrentTime() {
|
|
39
|
+
return new Date().toTimeString().slice(0, 8);
|
|
118
40
|
}
|
|
119
|
-
function
|
|
120
|
-
return
|
|
41
|
+
function getCurrentDatetime() {
|
|
42
|
+
return new Date().toISOString().replace("T", " ").slice(0, 19);
|
|
43
|
+
}
|
|
44
|
+
function evalFirst(arr, context, target) {
|
|
45
|
+
if (!Array.isArray(arr))
|
|
46
|
+
return undefined;
|
|
47
|
+
for (const item of arr) {
|
|
48
|
+
const result = evaluateValue(item, context, target);
|
|
49
|
+
if (result !== undefined)
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
function evalRule(rule, context, target) {
|
|
55
|
+
if (typeof rule !== "object" || rule === null)
|
|
56
|
+
return Boolean(rule);
|
|
57
|
+
const keys = Object.keys(rule);
|
|
58
|
+
if (keys.length === 1 && keys[0].startsWith("$") && Array.isArray(rule[keys[0]])) {
|
|
59
|
+
const op = keys[0];
|
|
60
|
+
const arg = rule[op];
|
|
61
|
+
if (arg.length === 2) {
|
|
62
|
+
return evalOperator(op, undefined, arg, target, context);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return matchesFilter(target, rule, context);
|
|
121
66
|
}
|
|
122
|
-
/**
|
|
123
|
-
* Evaluates a single field value against its condition.
|
|
124
|
-
* Condition can be a primitive (implicit $eq) or an operator object.
|
|
125
|
-
*/
|
|
126
67
|
function evalCondition(fieldVal, condition, target, context) {
|
|
127
68
|
if (condition === null || typeof condition !== "object") {
|
|
128
|
-
|
|
129
|
-
return fieldVal.includes(condition);
|
|
130
|
-
return fieldVal === condition;
|
|
69
|
+
return compareResolvedValues(fieldVal, evaluateValue(condition, context, target));
|
|
131
70
|
}
|
|
132
71
|
if (Array.isArray(condition)) {
|
|
133
|
-
return fieldVal
|
|
72
|
+
return compareResolvedValues(fieldVal, evaluateValue(condition, context, target));
|
|
134
73
|
}
|
|
135
74
|
const keys = Object.keys(condition);
|
|
75
|
+
if (keys.length === 1 && keys[0] === "$source") {
|
|
76
|
+
return compareResolvedValues(fieldVal, evaluateValue(condition, context, target));
|
|
77
|
+
}
|
|
136
78
|
for (const op of keys) {
|
|
137
79
|
const arg = condition[op];
|
|
138
80
|
if (!evalOperator(op, fieldVal, arg, target, context))
|
|
@@ -140,114 +82,112 @@ function evalCondition(fieldVal, condition, target, context) {
|
|
|
140
82
|
}
|
|
141
83
|
return true;
|
|
142
84
|
}
|
|
143
|
-
/**
|
|
144
|
-
* Evaluates a single operator against a field value.
|
|
145
|
-
*/
|
|
146
85
|
function evalOperator(op, fieldVal, arg, target, context) {
|
|
147
86
|
switch (op) {
|
|
148
87
|
case "$eq": {
|
|
149
88
|
if (Array.isArray(arg) && arg.length === 2) {
|
|
150
|
-
const left =
|
|
151
|
-
const right =
|
|
89
|
+
const left = evaluateValue(arg[0], context, target);
|
|
90
|
+
const right = evaluateValue(arg[1], context, target);
|
|
152
91
|
return left === right;
|
|
153
92
|
}
|
|
154
|
-
|
|
155
|
-
if (Array.isArray(fieldVal))
|
|
156
|
-
return fieldVal.includes(resolved);
|
|
157
|
-
return fieldVal === resolved;
|
|
158
|
-
}
|
|
159
|
-
case "$neq": {
|
|
160
|
-
const resolved = resolveValue(arg, target, context);
|
|
161
|
-
if (Array.isArray(fieldVal))
|
|
162
|
-
return !fieldVal.includes(resolved);
|
|
163
|
-
return fieldVal !== resolved;
|
|
93
|
+
return compareResolvedValues(fieldVal, evaluateValue(arg, context, target));
|
|
164
94
|
}
|
|
95
|
+
case "$neq":
|
|
96
|
+
return !compareResolvedValues(fieldVal, evaluateValue(arg, context, target));
|
|
165
97
|
case "$gt":
|
|
166
|
-
return fieldVal >
|
|
98
|
+
return fieldVal > evaluateValue(arg, context, target);
|
|
167
99
|
case "$gte":
|
|
168
|
-
return fieldVal >=
|
|
100
|
+
return fieldVal >= evaluateValue(arg, context, target);
|
|
169
101
|
case "$lt":
|
|
170
|
-
return fieldVal <
|
|
102
|
+
return fieldVal < evaluateValue(arg, context, target);
|
|
171
103
|
case "$lte":
|
|
172
|
-
return fieldVal <=
|
|
104
|
+
return fieldVal <= evaluateValue(arg, context, target);
|
|
173
105
|
case "$between": {
|
|
174
106
|
if (!Array.isArray(arg) || arg.length !== 2) {
|
|
175
107
|
throw new Error("$between requires an array of [min, max]");
|
|
176
108
|
}
|
|
177
|
-
return fieldVal >= arg[0]
|
|
178
|
-
&& fieldVal <= arg[1];
|
|
109
|
+
return fieldVal >= evaluateValue(arg[0], context, target)
|
|
110
|
+
&& fieldVal <= evaluateValue(arg[1], context, target);
|
|
111
|
+
}
|
|
112
|
+
case "$in": {
|
|
113
|
+
const resolved = evaluateValue(arg, context, target);
|
|
114
|
+
if (!Array.isArray(resolved))
|
|
115
|
+
return compareResolvedValues(fieldVal, resolved);
|
|
116
|
+
return compareResolvedValues(fieldVal, resolved);
|
|
179
117
|
}
|
|
180
|
-
case "$
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
118
|
+
case "$nin": {
|
|
119
|
+
const resolved = evaluateValue(arg, context, target);
|
|
120
|
+
if (!Array.isArray(resolved))
|
|
121
|
+
return !compareResolvedValues(fieldVal, resolved);
|
|
122
|
+
return !compareResolvedValues(fieldVal, resolved);
|
|
123
|
+
}
|
|
124
|
+
case "$ein": {
|
|
125
|
+
const resolved = evaluateValue(arg, context, target);
|
|
126
|
+
if (!Array.isArray(fieldVal) || !Array.isArray(resolved))
|
|
186
127
|
return false;
|
|
187
|
-
return fieldVal.every(v =>
|
|
188
|
-
|
|
189
|
-
|
|
128
|
+
return fieldVal.every((v) => resolved.includes(v));
|
|
129
|
+
}
|
|
130
|
+
case "$nein": {
|
|
131
|
+
const resolved = evaluateValue(arg, context, target);
|
|
132
|
+
if (!Array.isArray(fieldVal) || !Array.isArray(resolved))
|
|
190
133
|
return false;
|
|
191
|
-
return fieldVal.some(v => !
|
|
134
|
+
return fieldVal.some((v) => !resolved.includes(v));
|
|
135
|
+
}
|
|
192
136
|
case "$has":
|
|
193
|
-
return typeof fieldVal === "string"
|
|
194
|
-
&& fieldVal.includes(String(resolveValue(arg, target, context)));
|
|
137
|
+
return typeof fieldVal === "string" && fieldVal.includes(String(evaluateValue(arg, context, target)));
|
|
195
138
|
case "$nhas":
|
|
196
|
-
return typeof fieldVal === "string"
|
|
197
|
-
&& !fieldVal.includes(String(resolveValue(arg, target, context)));
|
|
139
|
+
return typeof fieldVal === "string" && !fieldVal.includes(String(evaluateValue(arg, context, target)));
|
|
198
140
|
case "$ihas": {
|
|
199
|
-
const needle = String(
|
|
141
|
+
const needle = String(evaluateValue(arg, context, target)).toLowerCase();
|
|
200
142
|
return typeof fieldVal === "string" && fieldVal.toLowerCase().includes(needle);
|
|
201
143
|
}
|
|
202
144
|
case "$nihas": {
|
|
203
|
-
const needle = String(
|
|
145
|
+
const needle = String(evaluateValue(arg, context, target)).toLowerCase();
|
|
204
146
|
return typeof fieldVal === "string" && !fieldVal.toLowerCase().includes(needle);
|
|
205
147
|
}
|
|
206
148
|
case "$sw":
|
|
207
|
-
return typeof fieldVal === "string" && fieldVal.startsWith(String(arg));
|
|
149
|
+
return typeof fieldVal === "string" && fieldVal.startsWith(String(evaluateValue(arg, context, target)));
|
|
208
150
|
case "$nsw":
|
|
209
|
-
return typeof fieldVal === "string" && !fieldVal.startsWith(String(arg));
|
|
151
|
+
return typeof fieldVal === "string" && !fieldVal.startsWith(String(evaluateValue(arg, context, target)));
|
|
210
152
|
case "$ew":
|
|
211
|
-
return typeof fieldVal === "string" && fieldVal.endsWith(String(arg));
|
|
153
|
+
return typeof fieldVal === "string" && fieldVal.endsWith(String(evaluateValue(arg, context, target)));
|
|
212
154
|
case "$new":
|
|
213
|
-
return typeof fieldVal === "string" && !fieldVal.endsWith(String(arg));
|
|
155
|
+
return typeof fieldVal === "string" && !fieldVal.endsWith(String(evaluateValue(arg, context, target)));
|
|
214
156
|
case "$regex":
|
|
215
|
-
return new RegExp(String(arg)).test(String(fieldVal));
|
|
157
|
+
return new RegExp(String(evaluateValue(arg, context, target))).test(String(fieldVal));
|
|
216
158
|
case "$iregex":
|
|
217
|
-
return new RegExp(String(arg), "i").test(String(fieldVal));
|
|
159
|
+
return new RegExp(String(evaluateValue(arg, context, target)), "i").test(String(fieldVal));
|
|
218
160
|
case "$exists":
|
|
219
161
|
return arg ? fieldVal !== undefined : fieldVal === undefined;
|
|
220
162
|
case "$and":
|
|
221
|
-
return Array.isArray(arg) && arg.every(cond => evalCondition(fieldVal, cond, target, context));
|
|
163
|
+
return Array.isArray(arg) && arg.every((cond) => evalCondition(fieldVal, cond, target, context));
|
|
222
164
|
case "$nand":
|
|
223
|
-
return Array.isArray(arg) && !arg.every(cond => evalCondition(fieldVal, cond, target, context));
|
|
165
|
+
return Array.isArray(arg) && !arg.every((cond) => evalCondition(fieldVal, cond, target, context));
|
|
224
166
|
case "$or":
|
|
225
|
-
return Array.isArray(arg) && arg.some(cond => evalCondition(fieldVal, cond, target, context));
|
|
167
|
+
return Array.isArray(arg) && arg.some((cond) => evalCondition(fieldVal, cond, target, context));
|
|
226
168
|
case "$nor":
|
|
227
|
-
return Array.isArray(arg) && !arg.some(cond => evalCondition(fieldVal, cond, target, context));
|
|
169
|
+
return Array.isArray(arg) && !arg.some((cond) => evalCondition(fieldVal, cond, target, context));
|
|
228
170
|
case "$not":
|
|
229
171
|
return !evalCondition(fieldVal, arg, target, context);
|
|
230
172
|
case "$some":
|
|
231
173
|
if (!Array.isArray(fieldVal))
|
|
232
174
|
return false;
|
|
233
|
-
return fieldVal.some(item => matchesFilter((item ?? {}), (arg ?? {}), context));
|
|
175
|
+
return fieldVal.some((item) => matchesFilter((item ?? {}), (arg ?? {}), context));
|
|
234
176
|
case "$nsome":
|
|
235
177
|
if (!Array.isArray(fieldVal))
|
|
236
178
|
return true;
|
|
237
|
-
return !fieldVal.some(item => matchesFilter((item ?? {}), (arg ?? {}), context));
|
|
179
|
+
return !fieldVal.some((item) => matchesFilter((item ?? {}), (arg ?? {}), context));
|
|
238
180
|
case "$every":
|
|
239
181
|
if (!Array.isArray(fieldVal))
|
|
240
182
|
return false;
|
|
241
|
-
return fieldVal.every(item => matchesFilter((item ?? {}), (arg ?? {}), context));
|
|
183
|
+
return fieldVal.every((item) => matchesFilter((item ?? {}), (arg ?? {}), context));
|
|
242
184
|
case "$nevery":
|
|
243
185
|
if (!Array.isArray(fieldVal))
|
|
244
186
|
return false;
|
|
245
|
-
return !fieldVal.every(item => matchesFilter((item ?? {}), (arg ?? {}), context));
|
|
187
|
+
return !fieldVal.every((item) => matchesFilter((item ?? {}), (arg ?? {}), context));
|
|
246
188
|
case "$length":
|
|
247
189
|
case "$size": {
|
|
248
|
-
const len = Array.isArray(fieldVal)
|
|
249
|
-
? fieldVal.length
|
|
250
|
-
: (typeof fieldVal === "string" ? fieldVal.length : undefined);
|
|
190
|
+
const len = Array.isArray(fieldVal) ? fieldVal.length : (typeof fieldVal === "string" ? fieldVal.length : undefined);
|
|
251
191
|
if (len === undefined)
|
|
252
192
|
return false;
|
|
253
193
|
return evalCondition(len, arg, target, context);
|
|
@@ -255,7 +195,7 @@ function evalOperator(op, fieldVal, arg, target, context) {
|
|
|
255
195
|
case "$sum": {
|
|
256
196
|
if (!Array.isArray(fieldVal))
|
|
257
197
|
return false;
|
|
258
|
-
const sum = fieldVal.reduce((
|
|
198
|
+
const sum = fieldVal.reduce((acc, item) => (Number(acc) || 0) + (Number(item) || 0), 0);
|
|
259
199
|
return evalCondition(sum, arg, target, context);
|
|
260
200
|
}
|
|
261
201
|
case "$date":
|
|
@@ -270,121 +210,149 @@ function evalOperator(op, fieldVal, arg, target, context) {
|
|
|
270
210
|
throw new Error(`Unknown operator: ${op}`);
|
|
271
211
|
}
|
|
272
212
|
}
|
|
273
|
-
|
|
274
|
-
* Vyhodnoti hodnotovy vyraz (napr. `$plus`, `$concat`, `$val`) v kontextu dat.
|
|
275
|
-
*
|
|
276
|
-
* @param expr Vyraz k vyhodnoceni.
|
|
277
|
-
* @param context Kontext pro reference jako `$ctx.*`.
|
|
278
|
-
* @returns Vysledna hodnota vyrazu.
|
|
279
|
-
*/
|
|
280
|
-
export function evaluateValue(expr, context = {}) {
|
|
213
|
+
export function evaluateValue(expr, context = {}, target = {}) {
|
|
281
214
|
if (expr === null || expr === undefined)
|
|
282
215
|
return expr;
|
|
283
|
-
if (typeof expr
|
|
284
|
-
return
|
|
216
|
+
if (typeof expr === "string") {
|
|
217
|
+
return resolveContextRef(resolveTargetRef(expr, target), context);
|
|
285
218
|
}
|
|
219
|
+
if (typeof expr !== "object")
|
|
220
|
+
return expr;
|
|
286
221
|
if (Array.isArray(expr)) {
|
|
287
|
-
return expr.map(
|
|
222
|
+
return expr.map((item) => evaluateValue(item, context, target));
|
|
288
223
|
}
|
|
289
224
|
const obj = expr;
|
|
290
225
|
const keys = Object.keys(obj);
|
|
226
|
+
if (keys.length === 1 && keys[0] === "$source") {
|
|
227
|
+
const source = obj["$source"];
|
|
228
|
+
const sourceResolver = context.sourceResolver;
|
|
229
|
+
if (!sourceResolver) {
|
|
230
|
+
throw new Error("$source requires context.sourceResolver");
|
|
231
|
+
}
|
|
232
|
+
const stack = context.sourceStack ?? [];
|
|
233
|
+
const sourceKey = buildSourceKey(source);
|
|
234
|
+
if (stack.includes(sourceKey)) {
|
|
235
|
+
throw new Error(`Cyclic $source reference detected for ${sourceKey}`);
|
|
236
|
+
}
|
|
237
|
+
return sourceResolver(source, { ...context, sourceStack: [...stack, sourceKey] });
|
|
238
|
+
}
|
|
291
239
|
if (keys.includes("$first")) {
|
|
292
|
-
return evalFirst(obj["$first"], context);
|
|
240
|
+
return evalFirst(obj["$first"], context, target);
|
|
293
241
|
}
|
|
294
242
|
if (keys.includes("$val") || keys.includes("$value")) {
|
|
295
243
|
const val = obj["$val"] !== undefined ? obj["$val"] : obj["$value"];
|
|
296
244
|
const rule = obj["$rule"];
|
|
297
|
-
if (rule !== undefined && !evalRule(rule, context))
|
|
245
|
+
if (rule !== undefined && !evalRule(rule, context, target))
|
|
298
246
|
return undefined;
|
|
299
|
-
return evaluateValue(val, context);
|
|
247
|
+
return evaluateValue(val, context, target);
|
|
300
248
|
}
|
|
301
249
|
if (keys.includes("$concat")) {
|
|
302
250
|
const list = obj["$concat"] || [];
|
|
303
|
-
return list.map(
|
|
251
|
+
return list.map((item) => String(evaluateValue(item, context, target))).join("");
|
|
304
252
|
}
|
|
305
253
|
if (keys.includes("$plus")) {
|
|
306
254
|
const list = obj["$plus"] || [];
|
|
307
|
-
return list.reduce((acc,
|
|
255
|
+
return list.reduce((acc, item) => Number(acc) + Number(evaluateValue(item, context, target)), 0);
|
|
308
256
|
}
|
|
309
257
|
if (keys.includes("$minus")) {
|
|
310
|
-
const vals = (obj["$minus"] || []).map(
|
|
311
|
-
return vals.slice(1).reduce((acc,
|
|
258
|
+
const vals = (obj["$minus"] || []).map((item) => Number(evaluateValue(item, context, target)));
|
|
259
|
+
return vals.slice(1).reduce((acc, item) => acc - item, vals[0]);
|
|
312
260
|
}
|
|
313
261
|
if (keys.includes("$times")) {
|
|
314
262
|
const list = obj["$times"] || [];
|
|
315
|
-
return list.reduce((acc,
|
|
263
|
+
return list.reduce((acc, item) => Number(acc) * Number(evaluateValue(item, context, target)), 1);
|
|
316
264
|
}
|
|
317
|
-
if (keys.includes("$div")) {
|
|
318
|
-
const
|
|
319
|
-
|
|
265
|
+
if (keys.includes("$div") || keys.includes("$divide")) {
|
|
266
|
+
const list = (obj["$div"] || obj["$divide"]) || [];
|
|
267
|
+
const vals = list.map((item) => Number(evaluateValue(item, context, target)));
|
|
268
|
+
return vals.slice(1).reduce((acc, item) => acc / item, vals[0]);
|
|
320
269
|
}
|
|
321
270
|
if (keys.includes("$min")) {
|
|
322
|
-
const vals = (obj["$min"] || []).map(
|
|
271
|
+
const vals = (obj["$min"] || []).map((item) => Number(evaluateValue(item, context, target)));
|
|
323
272
|
return Math.min(...vals);
|
|
324
273
|
}
|
|
325
274
|
if (keys.includes("$max")) {
|
|
326
|
-
const vals = (obj["$max"] || []).map(
|
|
275
|
+
const vals = (obj["$max"] || []).map((item) => Number(evaluateValue(item, context, target)));
|
|
327
276
|
return Math.max(...vals);
|
|
328
277
|
}
|
|
329
278
|
if (keys.includes("$avg")) {
|
|
330
|
-
const vals = (obj["$avg"] || []).map(
|
|
331
|
-
return vals.reduce((
|
|
279
|
+
const vals = (obj["$avg"] || []).map((item) => Number(evaluateValue(item, context, target)));
|
|
280
|
+
return vals.reduce((acc, item) => acc + item, 0) / vals.length;
|
|
332
281
|
}
|
|
333
282
|
if (keys.includes("$median")) {
|
|
334
|
-
const sorted = [...(obj["$median"] || []).map(
|
|
335
|
-
.sort((a, b) => a - b);
|
|
283
|
+
const sorted = [...(obj["$median"] || []).map((item) => Number(evaluateValue(item, context, target)))].sort((a, b) => a - b);
|
|
336
284
|
const mid = Math.floor(sorted.length / 2);
|
|
337
|
-
return sorted.length % 2 !== 0
|
|
338
|
-
? sorted[mid]
|
|
339
|
-
: (sorted[mid - 1] + sorted[mid]) / 2;
|
|
285
|
+
return sorted.length % 2 !== 0 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2;
|
|
340
286
|
}
|
|
341
287
|
if (keys.includes("$last")) {
|
|
342
288
|
const arr = obj["$last"];
|
|
343
|
-
return Array.isArray(arr) ? evaluateValue(arr[arr.length - 1], context) : evaluateValue(arr, context);
|
|
289
|
+
return Array.isArray(arr) ? evaluateValue(arr[arr.length - 1], context, target) : evaluateValue(arr, context, target);
|
|
344
290
|
}
|
|
345
|
-
return
|
|
291
|
+
return Object.fromEntries(Object.entries(obj).map(([key, value]) => [key, evaluateValue(value, context, target)]));
|
|
346
292
|
}
|
|
347
|
-
function
|
|
348
|
-
|
|
349
|
-
return undefined;
|
|
350
|
-
for (const item of arr) {
|
|
351
|
-
if (typeof item !== "object" || item === null)
|
|
352
|
-
return item;
|
|
353
|
-
const result = evaluateValue(item, context);
|
|
354
|
-
if (result !== undefined)
|
|
355
|
-
return result;
|
|
356
|
-
}
|
|
357
|
-
return undefined;
|
|
293
|
+
export function defineFilter(filter) {
|
|
294
|
+
return filter;
|
|
358
295
|
}
|
|
359
|
-
function
|
|
360
|
-
if (
|
|
361
|
-
return
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
const
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
296
|
+
export function matchesFilter(target, filter, context = {}) {
|
|
297
|
+
if (filter === null || filter === undefined)
|
|
298
|
+
return true;
|
|
299
|
+
if (typeof filter !== "object")
|
|
300
|
+
return Boolean(filter);
|
|
301
|
+
const keys = Object.keys(filter);
|
|
302
|
+
for (const key of keys) {
|
|
303
|
+
const val = filter[key];
|
|
304
|
+
if (key === "$and") {
|
|
305
|
+
if (!Array.isArray(val) || !val.every((condition) => matchesFilter(target, condition, context)))
|
|
306
|
+
return false;
|
|
307
|
+
continue;
|
|
370
308
|
}
|
|
309
|
+
if (key === "$nand") {
|
|
310
|
+
if (Array.isArray(val) && val.every((condition) => matchesFilter(target, condition, context)))
|
|
311
|
+
return false;
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
if (key === "$or") {
|
|
315
|
+
if (!Array.isArray(val) || !val.some((condition) => matchesFilter(target, condition, context)))
|
|
316
|
+
return false;
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
if (key === "$nor") {
|
|
320
|
+
if (Array.isArray(val) && val.some((condition) => matchesFilter(target, condition, context)))
|
|
321
|
+
return false;
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
if (key === "$not") {
|
|
325
|
+
if (matchesFilter(target, val, context))
|
|
326
|
+
return false;
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
if (key.startsWith("$ctx.") || (key.startsWith("$") && !key.startsWith("$this."))) {
|
|
330
|
+
const fieldVal = resolveContextRef(key, context);
|
|
331
|
+
if (!evalCondition(fieldVal, val, target, context))
|
|
332
|
+
return false;
|
|
333
|
+
continue;
|
|
334
|
+
}
|
|
335
|
+
if (key.startsWith("$this.")) {
|
|
336
|
+
const fieldVal = resolveTargetRef(key, target);
|
|
337
|
+
if (!evalCondition(fieldVal, val, target, context))
|
|
338
|
+
return false;
|
|
339
|
+
continue;
|
|
340
|
+
}
|
|
341
|
+
const optional = key.endsWith("?");
|
|
342
|
+
const cleanKey = optional ? key.slice(0, -1) : key;
|
|
343
|
+
const fieldVal = resolvePath(target, cleanKey);
|
|
344
|
+
if (optional && fieldVal === undefined)
|
|
345
|
+
continue;
|
|
346
|
+
if (!evalCondition(fieldVal, val, target, context))
|
|
347
|
+
return false;
|
|
371
348
|
}
|
|
372
|
-
return
|
|
373
|
-
}
|
|
374
|
-
function getCurrentDate() {
|
|
375
|
-
return new Date().toISOString().slice(0, 10);
|
|
376
|
-
}
|
|
377
|
-
function getCurrentTime() {
|
|
378
|
-
return new Date().toTimeString().slice(0, 8);
|
|
379
|
-
}
|
|
380
|
-
function getCurrentDatetime() {
|
|
381
|
-
return new Date().toISOString().replace("T", " ").slice(0, 19);
|
|
349
|
+
return true;
|
|
382
350
|
}
|
|
383
351
|
/**
|
|
384
352
|
* Vyfiltruje pole JSON dokumentu podle filtru.
|
|
385
353
|
*/
|
|
386
354
|
export function filterArray(documents, filter, context = {}) {
|
|
387
|
-
return documents.filter(doc => matchesFilter(doc, filter, context));
|
|
355
|
+
return documents.filter((doc) => matchesFilter(doc, filter, context));
|
|
388
356
|
}
|
|
389
357
|
/**
|
|
390
358
|
* Vyfiltruje mapu JSON dokumentu (`Record<string, doc>`) podle filtru.
|
package/dist/typecheck.js
CHANGED
package/dist/types.d.ts
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
/** Zakladni skalarny typ podporovany ve filtrech. */
|
|
2
2
|
export type scalarType = string | number | boolean | null;
|
|
3
3
|
/** Kontext predavany jako zdroj hodnot pro reference `$ctx.*`. */
|
|
4
|
-
export type contextType = Record<string, unknown
|
|
4
|
+
export type contextType = Record<string, unknown> & {
|
|
5
|
+
/** Volitelny resolver pro dynamicke reference typu `$source`. */
|
|
6
|
+
sourceResolver?: (source: unknown, context: contextType) => unknown;
|
|
7
|
+
/** Interni stack pro detekci cyklu pri reseni `$source`. */
|
|
8
|
+
sourceStack?: string[];
|
|
9
|
+
};
|
|
5
10
|
/** Cilovy JSON dokument, nad kterym se filtr vyhodnocuje. */
|
|
6
11
|
export type targetType = Record<string, unknown>;
|
|
7
12
|
type objectLikeType = Record<string, unknown>;
|
|
@@ -19,14 +24,30 @@ export type targetReferenceType<TTarget extends targetType> = `$this.${dotPathTy
|
|
|
19
24
|
/** Reference na hodnotu z kontextu, napr. `$ctx.user.role`. */
|
|
20
25
|
export type contextReferenceType<TContext extends contextType = contextType> = `$ctx.${dotPathType<TContext>}`;
|
|
21
26
|
export type valueReferenceType<TTarget extends targetType = targetType, TContext extends contextType = contextType> = targetReferenceType<TTarget> | contextReferenceType<TContext>;
|
|
27
|
+
/** Zdrojova reference, ktera se vyhodnoti na jednu hodnotu nebo pole hodnot. */
|
|
28
|
+
export type sourceValueType<TTarget extends targetType = targetType, TContext extends contextType = contextType> = {
|
|
29
|
+
$source: {
|
|
30
|
+
dataProvider: string;
|
|
31
|
+
filter?: filterType<TTarget, TContext>;
|
|
32
|
+
val: string;
|
|
33
|
+
map?: never;
|
|
34
|
+
};
|
|
35
|
+
} | {
|
|
36
|
+
$source: {
|
|
37
|
+
dataProvider: string;
|
|
38
|
+
filter?: filterType<TTarget, TContext>;
|
|
39
|
+
map: string;
|
|
40
|
+
val?: never;
|
|
41
|
+
};
|
|
42
|
+
};
|
|
22
43
|
/**
|
|
23
44
|
* Stringová hodnota nebo reference s validací.
|
|
24
45
|
* Pokud začíná `$this.` nebo `$ctx.`, musí být validní cesta.
|
|
25
46
|
* Jinak je to libovolný string (ne-reference).
|
|
26
47
|
*/
|
|
27
48
|
type stringOrValidReferenceType<TTarget extends targetType, TContext extends contextType> = `$this.${dotPathType<TTarget>}` | `$ctx.${dotPathType<TContext>}` | (string & {});
|
|
28
|
-
type scalarOrReferenceType<TValue, TTarget extends targetType, TContext extends contextType> = TValue extends string ? stringOrValidReferenceType<TTarget, TContext> : scalarFieldValueType<TValue> | valueReferenceType<TTarget, TContext>;
|
|
29
|
-
export type conditionType<TValue = unknown, TTarget extends targetType = targetType, TContext extends contextType = contextType> = scalarType | undefined | conditionObjectType<TValue, TTarget, TContext> | Array<conditionType<TValue, TTarget, TContext>>;
|
|
49
|
+
type scalarOrReferenceType<TValue, TTarget extends targetType, TContext extends contextType> = TValue extends string ? stringOrValidReferenceType<TTarget, TContext> : scalarFieldValueType<TValue> | valueReferenceType<TTarget, TContext> | sourceValueType<TTarget, TContext>;
|
|
50
|
+
export type conditionType<TValue = unknown, TTarget extends targetType = targetType, TContext extends contextType = contextType> = scalarType | undefined | sourceValueType<TTarget, TContext> | conditionObjectType<TValue, TTarget, TContext> | Array<conditionType<TValue, TTarget, TContext>>;
|
|
30
51
|
/** Objektova podoba podminky (operator -> hodnota). */
|
|
31
52
|
export type conditionObjectType<TValue = unknown, TTarget extends targetType = targetType, TContext extends contextType = contextType> = {
|
|
32
53
|
/**
|
|
@@ -47,13 +68,13 @@ export type conditionObjectType<TValue = unknown, TTarget extends targetType = t
|
|
|
47
68
|
/** Rozsah vcetne hranic (`min <= field <= max`). */
|
|
48
69
|
$between?: [unknown, unknown];
|
|
49
70
|
/** Hodnota je v seznamu (`field in list`). */
|
|
50
|
-
$in?: Array<scalarOrReferenceType<TValue, TTarget, TContext>>;
|
|
71
|
+
$in?: Array<scalarOrReferenceType<TValue, TTarget, TContext> | sourceValueType<TTarget, TContext>>;
|
|
51
72
|
/** Hodnota neni v seznamu (`field not in list`). */
|
|
52
|
-
$nin?: Array<scalarOrReferenceType<TValue, TTarget, TContext>>;
|
|
73
|
+
$nin?: Array<scalarOrReferenceType<TValue, TTarget, TContext> | sourceValueType<TTarget, TContext>>;
|
|
53
74
|
/** Vsechny prvky pole jsou v seznamu. */
|
|
54
|
-
$ein?: TValue extends readonly unknown[] ? Array<scalarOrReferenceType<arrayElementType<TValue>, TTarget, TContext>> : never;
|
|
75
|
+
$ein?: TValue extends readonly unknown[] ? Array<scalarOrReferenceType<arrayElementType<TValue>, TTarget, TContext> | sourceValueType<TTarget, TContext>> : never;
|
|
55
76
|
/** Alespon jeden prvek pole neni v seznamu. */
|
|
56
|
-
$nein?: TValue extends readonly unknown[] ? Array<scalarOrReferenceType<arrayElementType<TValue>, TTarget, TContext>> : never;
|
|
77
|
+
$nein?: TValue extends readonly unknown[] ? Array<scalarOrReferenceType<arrayElementType<TValue>, TTarget, TContext> | sourceValueType<TTarget, TContext>> : never;
|
|
57
78
|
/** String obsahuje podretezec (case-sensitive). */
|
|
58
79
|
$has?: scalarOrReferenceType<string, TTarget, TContext>;
|
|
59
80
|
/** String neobsahuje podretezec (case-sensitive). */
|
|
@@ -100,7 +121,13 @@ export type conditionObjectType<TValue = unknown, TTarget extends targetType = t
|
|
|
100
121
|
$size?: conditionType<number, TTarget, TContext>;
|
|
101
122
|
/** Soucet prvku pole a porovnani vysledku. */
|
|
102
123
|
$sum?: conditionType<number, TTarget, TContext>;
|
|
103
|
-
/** Porovnani s aktualnim datem (YYYY-MM-DD).
|
|
124
|
+
/** Porovnani s aktualnim datem (YYYY-MM-DD).
|
|
125
|
+
* @example
|
|
126
|
+
* // Porovná, jestli pole `dateFinish` je dříve než dnešní datum:
|
|
127
|
+
* {
|
|
128
|
+
* dateFinish: { $lte: { $date: {} } as any },
|
|
129
|
+
* }
|
|
130
|
+
*/
|
|
104
131
|
$date?: conditionType<string, TTarget, TContext>;
|
|
105
132
|
/** Porovnani s aktualnim casem (HH:mm:ss). */
|
|
106
133
|
$time?: conditionType<string, TTarget, TContext>;
|