chronolizer 0.1.0 → 0.2.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/README.md +70 -7
- package/dist/ast/constructors.d.mts +140 -0
- package/dist/ast/constructors.mjs +25 -0
- package/dist/ast/fold.d.mts +12 -0
- package/dist/ast/fold.mjs +51 -0
- package/dist/ast/normalize.d.mts +42 -0
- package/dist/ast/normalize.mjs +32 -0
- package/dist/ast/schemas.d.mts +87 -0
- package/dist/ast/schemas.mjs +74 -0
- package/dist/filter/codec.d.mts +92 -0
- package/dist/filter/codec.mjs +48 -0
- package/dist/filter/errors.d.mts +14 -0
- package/dist/filter/errors.mjs +10 -0
- package/dist/filter/expression.d.mts +8 -0
- package/dist/filter/expression.mjs +93 -0
- package/dist/filter/schema.d.mts +13 -0
- package/dist/filter/schema.mjs +11 -0
- package/dist/filter/transformation.d.mts +37 -0
- package/dist/filter/transformation.mjs +20 -0
- package/dist/index.d.mts +22 -969
- package/dist/index.mjs +22 -2305
- package/dist/language/errors.d.mts +38 -0
- package/dist/language/errors.mjs +30 -0
- package/dist/language/model.d.mts +239 -0
- package/dist/language/model.mjs +53 -0
- package/dist/language/registry.d.mts +17 -0
- package/dist/language/registry.mjs +134 -0
- package/dist/locales/cs.d.mts +10 -0
- package/dist/locales/cs.mjs +693 -0
- package/dist/locales/de.d.mts +10 -0
- package/dist/locales/de.mjs +828 -0
- package/dist/locales/en.d.mts +10 -0
- package/dist/locales/en.mjs +550 -0
- package/dist/locales/es.d.mts +10 -0
- package/dist/locales/es.mjs +660 -0
- package/dist/locales/fr.d.mts +10 -0
- package/dist/locales/fr.mjs +737 -0
- package/dist/locales/nl.d.mts +10 -0
- package/dist/locales/nl.mjs +652 -0
- package/dist/locales/pl.d.mts +10 -0
- package/dist/locales/pl.mjs +711 -0
- package/dist/locales/shared.mjs +289 -0
- package/dist/locales/tr.d.mts +10 -0
- package/dist/locales/tr.mjs +660 -0
- package/dist/natural/correction.d.mts +13 -0
- package/dist/natural/correction.mjs +73 -0
- package/dist/natural/format.d.mts +47 -0
- package/dist/natural/format.mjs +14 -0
- package/dist/natural/parse.d.mts +99 -0
- package/dist/natural/parse.mjs +66 -0
- package/dist/natural/policy.mjs +6 -0
- package/dist/natural/suggest.d.mts +53 -0
- package/dist/natural/suggest.mjs +24 -0
- package/dist/natural/suggestion.d.mts +4 -0
- package/dist/natural/suggestion.mjs +79 -0
- package/dist/natural/text.d.mts +5 -0
- package/dist/natural/text.mjs +5 -0
- package/dist/resolve/resolve.d.mts +79 -0
- package/dist/resolve/resolve.mjs +66 -0
- package/dist/resolve/schema.d.mts +59 -0
- package/dist/resolve/schema.mjs +28 -0
- package/package.json +15 -3
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { DateRangeExpr, InstantExpr } from "../ast/schemas.mjs";
|
|
2
|
+
import { FilterExpressionParseError, InvalidDateFilterError } from "./errors.mjs";
|
|
3
|
+
import { Effect } from "effect";
|
|
4
|
+
//#region src/filter/codec.d.ts
|
|
5
|
+
declare const parseFilter: (filter: {
|
|
6
|
+
readonly gt?: string;
|
|
7
|
+
readonly gte?: string;
|
|
8
|
+
readonly lt?: string;
|
|
9
|
+
readonly lte?: string;
|
|
10
|
+
}) => Effect.Effect<{
|
|
11
|
+
readonly _tag: "DateRange";
|
|
12
|
+
readonly lower: {
|
|
13
|
+
readonly _tag: "GreaterThan";
|
|
14
|
+
readonly value: InstantExpr;
|
|
15
|
+
} | {
|
|
16
|
+
readonly _tag: "GreaterThanOrEqual";
|
|
17
|
+
readonly value: InstantExpr;
|
|
18
|
+
};
|
|
19
|
+
readonly upper: {
|
|
20
|
+
readonly _tag: "LessThan";
|
|
21
|
+
readonly value: InstantExpr;
|
|
22
|
+
} | {
|
|
23
|
+
readonly _tag: "LessThanOrEqual";
|
|
24
|
+
readonly value: InstantExpr;
|
|
25
|
+
};
|
|
26
|
+
} | {
|
|
27
|
+
readonly _tag: "DateRange";
|
|
28
|
+
readonly lower: {
|
|
29
|
+
readonly _tag: "GreaterThan";
|
|
30
|
+
readonly value: InstantExpr;
|
|
31
|
+
} | {
|
|
32
|
+
readonly _tag: "GreaterThanOrEqual";
|
|
33
|
+
readonly value: InstantExpr;
|
|
34
|
+
};
|
|
35
|
+
readonly upper?: never;
|
|
36
|
+
} | {
|
|
37
|
+
readonly _tag: "DateRange";
|
|
38
|
+
readonly lower?: never;
|
|
39
|
+
readonly upper: {
|
|
40
|
+
readonly _tag: "LessThan";
|
|
41
|
+
readonly value: InstantExpr;
|
|
42
|
+
} | {
|
|
43
|
+
readonly _tag: "LessThanOrEqual";
|
|
44
|
+
readonly value: InstantExpr;
|
|
45
|
+
};
|
|
46
|
+
}, FilterExpressionParseError | InvalidDateFilterError, never>;
|
|
47
|
+
declare const formatFilter: (range: DateRangeExpr) => {
|
|
48
|
+
readonly gt?: string;
|
|
49
|
+
readonly gte?: string;
|
|
50
|
+
readonly lt?: string;
|
|
51
|
+
readonly lte?: string;
|
|
52
|
+
};
|
|
53
|
+
declare const rangeKey: (range: DateRangeExpr) => string;
|
|
54
|
+
declare const completePeriod: (start: InstantExpr, end: InstantExpr) => {
|
|
55
|
+
readonly _tag: "DateRange";
|
|
56
|
+
readonly lower: {
|
|
57
|
+
readonly _tag: "GreaterThan";
|
|
58
|
+
readonly value: InstantExpr;
|
|
59
|
+
} | {
|
|
60
|
+
readonly _tag: "GreaterThanOrEqual";
|
|
61
|
+
readonly value: InstantExpr;
|
|
62
|
+
};
|
|
63
|
+
readonly upper: {
|
|
64
|
+
readonly _tag: "LessThan";
|
|
65
|
+
readonly value: InstantExpr;
|
|
66
|
+
} | {
|
|
67
|
+
readonly _tag: "LessThanOrEqual";
|
|
68
|
+
readonly value: InstantExpr;
|
|
69
|
+
};
|
|
70
|
+
} | {
|
|
71
|
+
readonly _tag: "DateRange";
|
|
72
|
+
readonly lower: {
|
|
73
|
+
readonly _tag: "GreaterThan";
|
|
74
|
+
readonly value: InstantExpr;
|
|
75
|
+
} | {
|
|
76
|
+
readonly _tag: "GreaterThanOrEqual";
|
|
77
|
+
readonly value: InstantExpr;
|
|
78
|
+
};
|
|
79
|
+
readonly upper?: never;
|
|
80
|
+
} | {
|
|
81
|
+
readonly _tag: "DateRange";
|
|
82
|
+
readonly lower?: never;
|
|
83
|
+
readonly upper: {
|
|
84
|
+
readonly _tag: "LessThan";
|
|
85
|
+
readonly value: InstantExpr;
|
|
86
|
+
} | {
|
|
87
|
+
readonly _tag: "LessThanOrEqual";
|
|
88
|
+
readonly value: InstantExpr;
|
|
89
|
+
};
|
|
90
|
+
};
|
|
91
|
+
//#endregion
|
|
92
|
+
export { completePeriod, formatFilter, parseFilter, rangeKey };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { boundedRange, greaterThan, greaterThanOrEqual, lessThan, lessThanOrEqual, lowerOpenRange, upperOpenRange } from "../ast/constructors.mjs";
|
|
2
|
+
import { normalizeRange } from "../ast/normalize.mjs";
|
|
3
|
+
import { InvalidDateFilterError } from "./errors.mjs";
|
|
4
|
+
import { formatInstantExpression, parseInstantExpression } from "./expression.mjs";
|
|
5
|
+
import { DateFilter } from "./schema.mjs";
|
|
6
|
+
import { Effect, Match } from "effect";
|
|
7
|
+
//#region src/filter/codec.ts
|
|
8
|
+
const parseLower = (filter) => {
|
|
9
|
+
if (filter.gt !== void 0) return Effect.map(parseInstantExpression(filter.gt), greaterThan);
|
|
10
|
+
if (filter.gte !== void 0) return Effect.map(parseInstantExpression(filter.gte), greaterThanOrEqual);
|
|
11
|
+
return Effect.void;
|
|
12
|
+
};
|
|
13
|
+
const parseUpper = (filter) => {
|
|
14
|
+
if (filter.lt !== void 0) return Effect.map(parseInstantExpression(filter.lt), lessThan);
|
|
15
|
+
if (filter.lte !== void 0) return Effect.map(parseInstantExpression(filter.lte), lessThanOrEqual);
|
|
16
|
+
return Effect.void;
|
|
17
|
+
};
|
|
18
|
+
const parseFilter = Effect.fn("chronolizer.parseFilter")(function* (filter) {
|
|
19
|
+
const [lower, upper] = yield* Effect.all([parseLower(filter), parseUpper(filter)]);
|
|
20
|
+
if (lower !== void 0 && upper !== void 0) return normalizeRange(boundedRange(lower, upper));
|
|
21
|
+
if (lower !== void 0) return normalizeRange(lowerOpenRange(lower));
|
|
22
|
+
if (upper !== void 0) return normalizeRange(upperOpenRange(upper));
|
|
23
|
+
return yield* new InvalidDateFilterError({ message: "A date filter must contain at least one bound" });
|
|
24
|
+
});
|
|
25
|
+
const formatLower = Match.typeTags()({
|
|
26
|
+
GreaterThan: (bound) => DateFilter.make({ gt: formatInstantExpression(bound.value) }),
|
|
27
|
+
GreaterThanOrEqual: (bound) => DateFilter.make({ gte: formatInstantExpression(bound.value) })
|
|
28
|
+
});
|
|
29
|
+
const formatUpper = Match.typeTags()({
|
|
30
|
+
LessThan: (bound) => DateFilter.make({ lt: formatInstantExpression(bound.value) }),
|
|
31
|
+
LessThanOrEqual: (bound) => DateFilter.make({ lte: formatInstantExpression(bound.value) })
|
|
32
|
+
});
|
|
33
|
+
const formatFilter = (range) => {
|
|
34
|
+
const normalized = normalizeRange(range);
|
|
35
|
+
if (normalized.lower !== void 0 && normalized.upper !== void 0) return DateFilter.make({
|
|
36
|
+
...formatLower(normalized.lower),
|
|
37
|
+
...formatUpper(normalized.upper)
|
|
38
|
+
});
|
|
39
|
+
if (normalized.lower !== void 0) return formatLower(normalized.lower);
|
|
40
|
+
return formatUpper(normalized.upper);
|
|
41
|
+
};
|
|
42
|
+
const rangeKey = (range) => {
|
|
43
|
+
const filter = formatFilter(range);
|
|
44
|
+
return `${filter.gt ?? ""}|${filter.gte ?? ""}|${filter.lt ?? ""}|${filter.lte ?? ""}`;
|
|
45
|
+
};
|
|
46
|
+
const completePeriod = (start, end) => boundedRange(greaterThanOrEqual(start), lessThan(end));
|
|
47
|
+
//#endregion
|
|
48
|
+
export { completePeriod, formatFilter, parseFilter, rangeKey };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
//#region src/filter/errors.d.ts
|
|
3
|
+
declare const FilterExpressionParseError_base: Schema.Class<FilterExpressionParseError, Schema.TaggedStruct<"FilterExpressionParseError", {
|
|
4
|
+
readonly input: Schema.String;
|
|
5
|
+
readonly offset: Schema.Int;
|
|
6
|
+
readonly expected: Schema.String;
|
|
7
|
+
}>, import("effect/Cause").YieldableError>;
|
|
8
|
+
declare class FilterExpressionParseError extends FilterExpressionParseError_base {}
|
|
9
|
+
declare const InvalidDateFilterError_base: Schema.Class<InvalidDateFilterError, Schema.TaggedStruct<"InvalidDateFilterError", {
|
|
10
|
+
readonly message: Schema.String;
|
|
11
|
+
}>, import("effect/Cause").YieldableError>;
|
|
12
|
+
declare class InvalidDateFilterError extends InvalidDateFilterError_base {}
|
|
13
|
+
//#endregion
|
|
14
|
+
export { FilterExpressionParseError, InvalidDateFilterError };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
//#region src/filter/errors.ts
|
|
3
|
+
var FilterExpressionParseError = class extends Schema.TaggedError()("FilterExpressionParseError", {
|
|
4
|
+
input: Schema.String,
|
|
5
|
+
offset: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),
|
|
6
|
+
expected: Schema.String
|
|
7
|
+
}) {};
|
|
8
|
+
var InvalidDateFilterError = class extends Schema.TaggedError()("InvalidDateFilterError", { message: Schema.String }) {};
|
|
9
|
+
//#endregion
|
|
10
|
+
export { FilterExpressionParseError, InvalidDateFilterError };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { InstantExpr } from "../ast/schemas.mjs";
|
|
2
|
+
import { FilterExpressionParseError } from "./errors.mjs";
|
|
3
|
+
import { Effect } from "effect";
|
|
4
|
+
//#region src/filter/expression.d.ts
|
|
5
|
+
declare const parseInstantExpression: (input: string) => Effect.Effect<InstantExpr, FilterExpressionParseError, never>;
|
|
6
|
+
declare const formatInstantExpression: (expression: InstantExpr) => string;
|
|
7
|
+
//#endregion
|
|
8
|
+
export { formatInstantExpression, parseInstantExpression };
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { isIsoDate } from "../ast/schemas.mjs";
|
|
2
|
+
import { dateLiteral, now, shift, startOf } from "../ast/constructors.mjs";
|
|
3
|
+
import { foldInstant } from "../ast/fold.mjs";
|
|
4
|
+
import { normalizeInstant } from "../ast/normalize.mjs";
|
|
5
|
+
import { FilterExpressionParseError } from "./errors.mjs";
|
|
6
|
+
import { Effect } from "effect";
|
|
7
|
+
//#region src/filter/expression.ts
|
|
8
|
+
const unitFromSymbol = (symbol) => {
|
|
9
|
+
switch (symbol) {
|
|
10
|
+
case "d": return "day";
|
|
11
|
+
case "w": return "week";
|
|
12
|
+
case "M": return "month";
|
|
13
|
+
case "q": return "quarter";
|
|
14
|
+
case "y": return "year";
|
|
15
|
+
default: return;
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
const symbolFromUnit = (unit) => {
|
|
19
|
+
switch (unit) {
|
|
20
|
+
case "day": return "d";
|
|
21
|
+
case "week": return "w";
|
|
22
|
+
case "month": return "M";
|
|
23
|
+
case "quarter": return "q";
|
|
24
|
+
case "year": return "y";
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
const failAt = (input, offset, expected) => Effect.fail(new FilterExpressionParseError({
|
|
28
|
+
input,
|
|
29
|
+
offset,
|
|
30
|
+
expected
|
|
31
|
+
}));
|
|
32
|
+
const isDigit = (value) => value >= "0" && value <= "9";
|
|
33
|
+
const isFirstPositiveDigit = (value) => value >= "1" && value <= "9";
|
|
34
|
+
const parseInstantExpression = Effect.fn(function* (input) {
|
|
35
|
+
let cursor = 0;
|
|
36
|
+
let expression;
|
|
37
|
+
let fixedAnchor = false;
|
|
38
|
+
if (input.startsWith("now")) {
|
|
39
|
+
expression = now();
|
|
40
|
+
cursor = 3;
|
|
41
|
+
} else {
|
|
42
|
+
const candidate = input.slice(0, 10);
|
|
43
|
+
if (!isIsoDate(candidate)) return yield* failAt(input, 0, "\"now\" or an ISO date (YYYY-MM-DD)");
|
|
44
|
+
expression = dateLiteral(candidate);
|
|
45
|
+
cursor = 10;
|
|
46
|
+
fixedAnchor = true;
|
|
47
|
+
}
|
|
48
|
+
if (fixedAnchor && cursor < input.length) {
|
|
49
|
+
if (input.slice(cursor, cursor + 2) !== "||") return yield* failAt(input, cursor, "\"||\" before date operations");
|
|
50
|
+
cursor += 2;
|
|
51
|
+
if (cursor === input.length) return yield* failAt(input, cursor, "an operation beginning with \"+\", \"-\", or \"/\"");
|
|
52
|
+
}
|
|
53
|
+
while (cursor < input.length) {
|
|
54
|
+
const operator = input[cursor];
|
|
55
|
+
if (operator === "/") {
|
|
56
|
+
const unit = unitFromSymbol(input[cursor + 1] ?? "");
|
|
57
|
+
if (unit === void 0) return yield* failAt(input, cursor + 1, "a date unit: d, w, M, q, or y");
|
|
58
|
+
expression = startOf(expression, unit);
|
|
59
|
+
cursor += 2;
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
if (operator !== "+" && operator !== "-") return yield* failAt(input, cursor, "an operation beginning with \"+\", \"-\", or \"/\"");
|
|
63
|
+
const amountStart = cursor + 1;
|
|
64
|
+
if (!isFirstPositiveDigit(input[amountStart] ?? "")) return yield* failAt(input, amountStart, "a positive integer without a leading zero");
|
|
65
|
+
cursor = amountStart + 1;
|
|
66
|
+
while (cursor < input.length && isDigit(input[cursor] ?? "")) cursor += 1;
|
|
67
|
+
const amount = Number(input.slice(amountStart, cursor));
|
|
68
|
+
if (!Number.isSafeInteger(amount)) return yield* failAt(input, amountStart, "a safe positive integer");
|
|
69
|
+
const unit = unitFromSymbol(input[cursor] ?? "");
|
|
70
|
+
if (unit === void 0) return yield* failAt(input, cursor, "a date unit: d, w, M, q, or y");
|
|
71
|
+
expression = shift(expression, operator === "+" ? amount : -amount, unit);
|
|
72
|
+
cursor += 1;
|
|
73
|
+
}
|
|
74
|
+
return expression;
|
|
75
|
+
});
|
|
76
|
+
const appendOperation = (base, operation) => ({
|
|
77
|
+
text: `${base.text}${base.fixedAnchor ? "||" : ""}${operation}`,
|
|
78
|
+
fixedAnchor: false
|
|
79
|
+
});
|
|
80
|
+
const formatInstantExpression = (expression) => foldInstant(normalizeInstant(expression), {
|
|
81
|
+
now: () => ({
|
|
82
|
+
text: "now",
|
|
83
|
+
fixedAnchor: false
|
|
84
|
+
}),
|
|
85
|
+
dateLiteral: (value) => ({
|
|
86
|
+
text: value,
|
|
87
|
+
fixedAnchor: true
|
|
88
|
+
}),
|
|
89
|
+
shift: (base, amount, unit) => appendOperation(base, `${amount < 0 ? "-" : "+"}${Math.abs(amount)}${symbolFromUnit(unit)}`),
|
|
90
|
+
startOf: (base, unit) => appendOperation(base, `/${symbolFromUnit(unit)}`)
|
|
91
|
+
}).text;
|
|
92
|
+
//#endregion
|
|
93
|
+
export { formatInstantExpression, parseInstantExpression };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
//#region src/filter/schema.d.ts
|
|
3
|
+
declare const DateExpressionString: Schema.String;
|
|
4
|
+
type DateExpressionString = typeof DateExpressionString.Type;
|
|
5
|
+
declare const DateFilter: Schema.Struct<{
|
|
6
|
+
readonly gt: Schema.optionalKey<Schema.String>;
|
|
7
|
+
readonly gte: Schema.optionalKey<Schema.String>;
|
|
8
|
+
readonly lt: Schema.optionalKey<Schema.String>;
|
|
9
|
+
readonly lte: Schema.optionalKey<Schema.String>;
|
|
10
|
+
}>;
|
|
11
|
+
type DateFilter = typeof DateFilter.Type;
|
|
12
|
+
//#endregion
|
|
13
|
+
export { DateExpressionString, DateFilter };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
//#region src/filter/schema.ts
|
|
3
|
+
const DateExpressionString = Schema.String.check(Schema.isMinLength(1)).annotate({ identifier: "DateExpressionString" });
|
|
4
|
+
const DateFilter = Schema.Struct({
|
|
5
|
+
gt: Schema.optionalKey(DateExpressionString),
|
|
6
|
+
gte: Schema.optionalKey(DateExpressionString),
|
|
7
|
+
lt: Schema.optionalKey(DateExpressionString),
|
|
8
|
+
lte: Schema.optionalKey(DateExpressionString)
|
|
9
|
+
}).check(Schema.makeFilter((filter) => !(filter.gt !== void 0 && filter.gte !== void 0), { expected: "at most one lower date bound" }), Schema.makeFilter((filter) => !(filter.lt !== void 0 && filter.lte !== void 0), { expected: "at most one upper date bound" }), Schema.makeFilter((filter) => filter.gt !== void 0 || filter.gte !== void 0 || filter.lt !== void 0 || filter.lte !== void 0, { expected: "at least one date bound" })).annotate({ identifier: "DateFilter" });
|
|
10
|
+
//#endregion
|
|
11
|
+
export { DateExpressionString, DateFilter };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { InstantExpr } from "../ast/schemas.mjs";
|
|
2
|
+
import { Schema } from "effect";
|
|
3
|
+
//#region src/filter/transformation.d.ts
|
|
4
|
+
declare const InstantExpressionFromString: Schema.decodeTo<Schema.Codec<InstantExpr, InstantExpr, never, never>, Schema.String, never, never>;
|
|
5
|
+
declare const DateRangeFromFilter: Schema.decodeTo<Schema.Union<readonly [Schema.TaggedStruct<"DateRange", {
|
|
6
|
+
readonly lower: Schema.Union<readonly [Schema.TaggedStruct<"GreaterThan", {
|
|
7
|
+
readonly value: Schema.Codec<InstantExpr, InstantExpr, never, never>;
|
|
8
|
+
}>, Schema.TaggedStruct<"GreaterThanOrEqual", {
|
|
9
|
+
readonly value: Schema.Codec<InstantExpr, InstantExpr, never, never>;
|
|
10
|
+
}>]>;
|
|
11
|
+
readonly upper: Schema.Union<readonly [Schema.TaggedStruct<"LessThan", {
|
|
12
|
+
readonly value: Schema.Codec<InstantExpr, InstantExpr, never, never>;
|
|
13
|
+
}>, Schema.TaggedStruct<"LessThanOrEqual", {
|
|
14
|
+
readonly value: Schema.Codec<InstantExpr, InstantExpr, never, never>;
|
|
15
|
+
}>]>;
|
|
16
|
+
}>, Schema.TaggedStruct<"DateRange", {
|
|
17
|
+
readonly lower: Schema.Union<readonly [Schema.TaggedStruct<"GreaterThan", {
|
|
18
|
+
readonly value: Schema.Codec<InstantExpr, InstantExpr, never, never>;
|
|
19
|
+
}>, Schema.TaggedStruct<"GreaterThanOrEqual", {
|
|
20
|
+
readonly value: Schema.Codec<InstantExpr, InstantExpr, never, never>;
|
|
21
|
+
}>]>;
|
|
22
|
+
readonly upper: Schema.optionalKey<Schema.Never>;
|
|
23
|
+
}>, Schema.TaggedStruct<"DateRange", {
|
|
24
|
+
readonly lower: Schema.optionalKey<Schema.Never>;
|
|
25
|
+
readonly upper: Schema.Union<readonly [Schema.TaggedStruct<"LessThan", {
|
|
26
|
+
readonly value: Schema.Codec<InstantExpr, InstantExpr, never, never>;
|
|
27
|
+
}>, Schema.TaggedStruct<"LessThanOrEqual", {
|
|
28
|
+
readonly value: Schema.Codec<InstantExpr, InstantExpr, never, never>;
|
|
29
|
+
}>]>;
|
|
30
|
+
}>]>, Schema.Struct<{
|
|
31
|
+
readonly gt: Schema.optionalKey<Schema.String>;
|
|
32
|
+
readonly gte: Schema.optionalKey<Schema.String>;
|
|
33
|
+
readonly lt: Schema.optionalKey<Schema.String>;
|
|
34
|
+
readonly lte: Schema.optionalKey<Schema.String>;
|
|
35
|
+
}>, never, never>;
|
|
36
|
+
//#endregion
|
|
37
|
+
export { DateRangeFromFilter, InstantExpressionFromString };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { DateRangeExpr, InstantExpr } from "../ast/schemas.mjs";
|
|
2
|
+
import { formatInstantExpression, parseInstantExpression } from "./expression.mjs";
|
|
3
|
+
import { DateFilter } from "./schema.mjs";
|
|
4
|
+
import { formatFilter, parseFilter } from "./codec.mjs";
|
|
5
|
+
import { Effect, Match, Schema, SchemaIssue, SchemaTransformation } from "effect";
|
|
6
|
+
//#region src/filter/transformation.ts
|
|
7
|
+
const expressionIssue = (input, offset, expected) => new SchemaIssue.Forbidden({ message: `Invalid date expression at offset ${offset}: expected ${expected}; input: ${input}` });
|
|
8
|
+
const InstantExpressionFromString = Schema.String.pipe(Schema.decodeTo(InstantExpr, SchemaTransformation.transformOrFail({
|
|
9
|
+
decode: (input) => Effect.mapError(parseInstantExpression(input), (error) => expressionIssue(error.input, error.offset, error.expected)),
|
|
10
|
+
encode: (expression) => Effect.succeed(formatInstantExpression(expression))
|
|
11
|
+
})));
|
|
12
|
+
const DateRangeFromFilter = DateFilter.pipe(Schema.decodeTo(DateRangeExpr, SchemaTransformation.transformOrFail({
|
|
13
|
+
decode: (filter) => Effect.mapError(parseFilter(filter), (error) => new SchemaIssue.Forbidden({ message: Match.valueTags(error, {
|
|
14
|
+
FilterExpressionParseError: (parseError) => `Invalid filter expression at offset ${parseError.offset}: expected ${parseError.expected}`,
|
|
15
|
+
InvalidDateFilterError: (filterError) => filterError.message
|
|
16
|
+
}) })),
|
|
17
|
+
encode: (range) => Effect.succeed(formatFilter(range))
|
|
18
|
+
})));
|
|
19
|
+
//#endregion
|
|
20
|
+
export { DateRangeFromFilter, InstantExpressionFromString };
|