chronolizer 0.1.0 → 0.3.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.
Files changed (62) hide show
  1. package/README.md +184 -119
  2. package/dist/ast/constructors.d.mts +135 -0
  3. package/dist/ast/constructors.mjs +25 -0
  4. package/dist/ast/fold.d.mts +13 -0
  5. package/dist/ast/fold.mjs +58 -0
  6. package/dist/ast/normalize.d.mts +42 -0
  7. package/dist/ast/normalize.mjs +34 -0
  8. package/dist/ast/schemas.d.mts +80 -0
  9. package/dist/ast/schemas.mjs +65 -0
  10. package/dist/filter/codec.d.mts +92 -0
  11. package/dist/filter/codec.mjs +48 -0
  12. package/dist/filter/errors.d.mts +14 -0
  13. package/dist/filter/errors.mjs +10 -0
  14. package/dist/filter/expression.d.mts +8 -0
  15. package/dist/filter/expression.mjs +76 -0
  16. package/dist/filter/schema.d.mts +13 -0
  17. package/dist/filter/schema.mjs +11 -0
  18. package/dist/filter/transformation.d.mts +37 -0
  19. package/dist/filter/transformation.mjs +20 -0
  20. package/dist/index.d.mts +22 -969
  21. package/dist/index.mjs +22 -2305
  22. package/dist/language/errors.d.mts +38 -0
  23. package/dist/language/errors.mjs +30 -0
  24. package/dist/language/model.d.mts +239 -0
  25. package/dist/language/model.mjs +50 -0
  26. package/dist/language/registry.d.mts +17 -0
  27. package/dist/language/registry.mjs +116 -0
  28. package/dist/locales/cs.d.mts +10 -0
  29. package/dist/locales/cs.mjs +746 -0
  30. package/dist/locales/de.d.mts +10 -0
  31. package/dist/locales/de.mjs +1039 -0
  32. package/dist/locales/en.d.mts +10 -0
  33. package/dist/locales/en.mjs +598 -0
  34. package/dist/locales/es.d.mts +10 -0
  35. package/dist/locales/es.mjs +908 -0
  36. package/dist/locales/fr.d.mts +10 -0
  37. package/dist/locales/fr.mjs +901 -0
  38. package/dist/locales/nl.d.mts +10 -0
  39. package/dist/locales/nl.mjs +791 -0
  40. package/dist/locales/pl.d.mts +10 -0
  41. package/dist/locales/pl.mjs +885 -0
  42. package/dist/locales/shared.mjs +395 -0
  43. package/dist/locales/tr.d.mts +10 -0
  44. package/dist/locales/tr.mjs +725 -0
  45. package/dist/natural/correction.d.mts +13 -0
  46. package/dist/natural/correction.mjs +73 -0
  47. package/dist/natural/format.d.mts +47 -0
  48. package/dist/natural/format.mjs +14 -0
  49. package/dist/natural/parse.d.mts +99 -0
  50. package/dist/natural/parse.mjs +66 -0
  51. package/dist/natural/policy.mjs +6 -0
  52. package/dist/natural/suggest.d.mts +53 -0
  53. package/dist/natural/suggest.mjs +24 -0
  54. package/dist/natural/suggestion.d.mts +4 -0
  55. package/dist/natural/suggestion.mjs +79 -0
  56. package/dist/natural/text.d.mts +5 -0
  57. package/dist/natural/text.mjs +5 -0
  58. package/dist/resolve/resolve.d.mts +79 -0
  59. package/dist/resolve/resolve.mjs +58 -0
  60. package/dist/resolve/schema.d.mts +59 -0
  61. package/dist/resolve/schema.mjs +28 -0
  62. package/package.json +15 -3
@@ -0,0 +1,34 @@
1
+ import { Shift } from "./schemas.mjs";
2
+ import { boundedRange, dateLiteral, greaterThan, greaterThanOrEqual, lessThan, lessThanOrEqual, lowerOpenRange, now, shift, startOf, upperOpenRange } from "./constructors.mjs";
3
+ import { Match, Schema } from "effect";
4
+ //#region src/ast/normalize.ts
5
+ const isShift = Schema.is(Shift);
6
+ const normalizeInstant = (expression) => Match.valueTags(expression, {
7
+ Now: () => now(),
8
+ DateLiteral: (literal) => dateLiteral(literal.value),
9
+ StartOf: (operation) => startOf(normalizeInstant(operation.base), operation.unit),
10
+ Shift: (operation) => {
11
+ const base = normalizeInstant(operation.base);
12
+ if (operation.amount === 0) return base;
13
+ if (isShift(base) && base.unit === operation.unit) {
14
+ const amount = base.amount + operation.amount;
15
+ if (Number.isSafeInteger(amount)) return normalizeInstant(shift(base.base, amount, operation.unit));
16
+ }
17
+ return shift(base, operation.amount, operation.unit);
18
+ }
19
+ });
20
+ const normalizeLower = (bound) => Match.valueTags(bound, {
21
+ GreaterThan: (value) => greaterThan(normalizeInstant(value.value)),
22
+ GreaterThanOrEqual: (value) => greaterThanOrEqual(normalizeInstant(value.value))
23
+ });
24
+ const normalizeUpper = (bound) => Match.valueTags(bound, {
25
+ LessThan: (value) => lessThan(normalizeInstant(value.value)),
26
+ LessThanOrEqual: (value) => lessThanOrEqual(normalizeInstant(value.value))
27
+ });
28
+ const normalizeRange = (range) => {
29
+ if (range.lower !== void 0 && range.upper !== void 0) return boundedRange(normalizeLower(range.lower), normalizeUpper(range.upper));
30
+ if (range.lower !== void 0) return lowerOpenRange(normalizeLower(range.lower));
31
+ return upperOpenRange(normalizeUpper(range.upper));
32
+ };
33
+ //#endregion
34
+ export { normalizeInstant, normalizeRange };
@@ -0,0 +1,80 @@
1
+ import { Schema } from "effect";
2
+ //#region src/ast/schemas.d.ts
3
+ declare const Unit: Schema.Literals<readonly ["day", "week", "month", "quarter", "year"]>;
4
+ type Unit = typeof Unit.Type;
5
+ declare const daysInMonth: (year: number, month: number) => number;
6
+ declare const isIsoDate: (value: string) => boolean;
7
+ declare const IsoDate: Schema.String;
8
+ type IsoDate = typeof IsoDate.Type;
9
+ declare const Now_base: Schema.Class<Now, Schema.TaggedStruct<"Now", {}>, {}>;
10
+ declare class Now extends Now_base {}
11
+ declare const DateLiteral_base: Schema.Class<DateLiteral, Schema.TaggedStruct<"DateLiteral", {
12
+ readonly value: Schema.String;
13
+ }>, {}>;
14
+ declare class DateLiteral extends DateLiteral_base {}
15
+ declare const Shift_base: Schema.Class<Shift, Schema.TaggedStruct<"Shift", {
16
+ readonly base: Schema.suspend<Schema.Codec<InstantExpr, InstantExpr, never, never>>;
17
+ readonly amount: Schema.Int;
18
+ readonly unit: Schema.Literals<readonly ["day", "week", "month", "quarter", "year"]>;
19
+ }>, {}>;
20
+ declare class Shift extends Shift_base {}
21
+ declare const StartOf_base: Schema.Class<StartOf, Schema.TaggedStruct<"StartOf", {
22
+ readonly base: Schema.suspend<Schema.Codec<InstantExpr, InstantExpr, never, never>>;
23
+ readonly unit: Schema.Literals<readonly ["day", "week", "month", "quarter", "year"]>;
24
+ }>, {}>;
25
+ declare class StartOf extends StartOf_base {}
26
+ type InstantExpr = Now | DateLiteral | Shift | StartOf;
27
+ declare const InstantExpr: Schema.Codec<InstantExpr>;
28
+ declare const GreaterThan: Schema.TaggedStruct<"GreaterThan", {
29
+ readonly value: Schema.Codec<InstantExpr, InstantExpr, never, never>;
30
+ }>;
31
+ declare const GreaterThanOrEqual: Schema.TaggedStruct<"GreaterThanOrEqual", {
32
+ readonly value: Schema.Codec<InstantExpr, InstantExpr, never, never>;
33
+ }>;
34
+ declare const LessThan: Schema.TaggedStruct<"LessThan", {
35
+ readonly value: Schema.Codec<InstantExpr, InstantExpr, never, never>;
36
+ }>;
37
+ declare const LessThanOrEqual: Schema.TaggedStruct<"LessThanOrEqual", {
38
+ readonly value: Schema.Codec<InstantExpr, InstantExpr, never, never>;
39
+ }>;
40
+ declare const LowerBound: Schema.Union<readonly [Schema.TaggedStruct<"GreaterThan", {
41
+ readonly value: Schema.Codec<InstantExpr, InstantExpr, never, never>;
42
+ }>, Schema.TaggedStruct<"GreaterThanOrEqual", {
43
+ readonly value: Schema.Codec<InstantExpr, InstantExpr, never, never>;
44
+ }>]>;
45
+ type LowerBound = typeof LowerBound.Type;
46
+ declare const UpperBound: Schema.Union<readonly [Schema.TaggedStruct<"LessThan", {
47
+ readonly value: Schema.Codec<InstantExpr, InstantExpr, never, never>;
48
+ }>, Schema.TaggedStruct<"LessThanOrEqual", {
49
+ readonly value: Schema.Codec<InstantExpr, InstantExpr, never, never>;
50
+ }>]>;
51
+ type UpperBound = typeof UpperBound.Type;
52
+ declare const DateRangeExpr: Schema.Union<readonly [Schema.TaggedStruct<"DateRange", {
53
+ readonly lower: Schema.Union<readonly [Schema.TaggedStruct<"GreaterThan", {
54
+ readonly value: Schema.Codec<InstantExpr, InstantExpr, never, never>;
55
+ }>, Schema.TaggedStruct<"GreaterThanOrEqual", {
56
+ readonly value: Schema.Codec<InstantExpr, InstantExpr, never, never>;
57
+ }>]>;
58
+ readonly upper: Schema.Union<readonly [Schema.TaggedStruct<"LessThan", {
59
+ readonly value: Schema.Codec<InstantExpr, InstantExpr, never, never>;
60
+ }>, Schema.TaggedStruct<"LessThanOrEqual", {
61
+ readonly value: Schema.Codec<InstantExpr, InstantExpr, never, never>;
62
+ }>]>;
63
+ }>, Schema.TaggedStruct<"DateRange", {
64
+ readonly lower: Schema.Union<readonly [Schema.TaggedStruct<"GreaterThan", {
65
+ readonly value: Schema.Codec<InstantExpr, InstantExpr, never, never>;
66
+ }>, Schema.TaggedStruct<"GreaterThanOrEqual", {
67
+ readonly value: Schema.Codec<InstantExpr, InstantExpr, never, never>;
68
+ }>]>;
69
+ readonly upper: Schema.optionalKey<Schema.Never>;
70
+ }>, Schema.TaggedStruct<"DateRange", {
71
+ readonly lower: Schema.optionalKey<Schema.Never>;
72
+ readonly upper: Schema.Union<readonly [Schema.TaggedStruct<"LessThan", {
73
+ readonly value: Schema.Codec<InstantExpr, InstantExpr, never, never>;
74
+ }>, Schema.TaggedStruct<"LessThanOrEqual", {
75
+ readonly value: Schema.Codec<InstantExpr, InstantExpr, never, never>;
76
+ }>]>;
77
+ }>]>;
78
+ type DateRangeExpr = typeof DateRangeExpr.Type;
79
+ //#endregion
80
+ export { DateLiteral, DateRangeExpr, GreaterThan, GreaterThanOrEqual, InstantExpr, IsoDate, LessThan, LessThanOrEqual, LowerBound, Now, Shift, StartOf, Unit, UpperBound, daysInMonth, isIsoDate };
@@ -0,0 +1,65 @@
1
+ import { Match, Option, Schema, String } from "effect";
2
+ //#region src/ast/schemas.ts
3
+ const Unit = Schema.Literals([
4
+ "day",
5
+ "week",
6
+ "month",
7
+ "quarter",
8
+ "year"
9
+ ]);
10
+ const isLeapYear = (year) => year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
11
+ const daysInMonth = (year, month) => Match.value(month).pipe(Match.when(2, () => isLeapYear(year) ? 29 : 28), Match.when(Match.is(4, 6, 9, 11), () => 30), Match.orElse(() => 31));
12
+ const isIsoDate = (value) => {
13
+ if (Option.isNone(String.match(/^\d{4}-\d{2}-\d{2}$/)(value))) return false;
14
+ const year = Number(value.slice(0, 4));
15
+ const month = Number(value.slice(5, 7));
16
+ const day = Number(value.slice(8, 10));
17
+ return month >= 1 && month <= 12 && day >= 1 && day <= daysInMonth(year, month);
18
+ };
19
+ const IsoDate = Schema.String.check(Schema.makeFilter(isIsoDate, { expected: "an ISO calendar date (YYYY-MM-DD)" })).annotate({ identifier: "IsoDate" });
20
+ var Now = class extends Schema.TaggedClass()("Now", {}) {};
21
+ var DateLiteral = class extends Schema.TaggedClass()("DateLiteral", { value: IsoDate }) {};
22
+ const ShiftAmount = Schema.Int.check(Schema.isBetween({
23
+ minimum: Number.MIN_SAFE_INTEGER,
24
+ maximum: Number.MAX_SAFE_INTEGER
25
+ }));
26
+ var Shift = class extends Schema.TaggedClass()("Shift", {
27
+ base: Schema.suspend(() => InstantExpr),
28
+ amount: ShiftAmount,
29
+ unit: Unit
30
+ }) {};
31
+ var StartOf = class extends Schema.TaggedClass()("StartOf", {
32
+ base: Schema.suspend(() => InstantExpr),
33
+ unit: Unit
34
+ }) {};
35
+ const InstantExpr = Schema.suspend(() => Schema.Union([
36
+ Now,
37
+ DateLiteral,
38
+ Shift,
39
+ StartOf
40
+ ])).annotate({ identifier: "InstantExpr" });
41
+ const GreaterThan = Schema.TaggedStruct("GreaterThan", { value: InstantExpr });
42
+ const GreaterThanOrEqual = Schema.TaggedStruct("GreaterThanOrEqual", { value: InstantExpr });
43
+ const LessThan = Schema.TaggedStruct("LessThan", { value: InstantExpr });
44
+ const LessThanOrEqual = Schema.TaggedStruct("LessThanOrEqual", { value: InstantExpr });
45
+ const LowerBound = Schema.Union([GreaterThan, GreaterThanOrEqual]);
46
+ const UpperBound = Schema.Union([LessThan, LessThanOrEqual]);
47
+ const BoundedDateRange = Schema.TaggedStruct("DateRange", {
48
+ lower: LowerBound,
49
+ upper: UpperBound
50
+ });
51
+ const LowerOpenDateRange = Schema.TaggedStruct("DateRange", {
52
+ lower: LowerBound,
53
+ upper: Schema.optionalKey(Schema.Never)
54
+ });
55
+ const UpperOpenDateRange = Schema.TaggedStruct("DateRange", {
56
+ lower: Schema.optionalKey(Schema.Never),
57
+ upper: UpperBound
58
+ });
59
+ const DateRangeExpr = Schema.Union([
60
+ BoundedDateRange,
61
+ LowerOpenDateRange,
62
+ UpperOpenDateRange
63
+ ]).annotate({ identifier: "DateRangeExpr" });
64
+ //#endregion
65
+ export { DateLiteral, DateRangeExpr, GreaterThan, GreaterThanOrEqual, InstantExpr, IsoDate, LessThan, LessThanOrEqual, LowerBound, Now, Shift, StartOf, Unit, UpperBound, daysInMonth, isIsoDate };
@@ -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,76 @@
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, Match } from "effect";
7
+ //#region src/filter/expression.ts
8
+ const unitFromSymbol = (symbol) => Match.value(symbol).pipe(Match.when("d", () => "day"), Match.when("w", () => "week"), Match.when("M", () => "month"), Match.when("q", () => "quarter"), Match.when("y", () => "year"), Match.orElse(() => void 0));
9
+ const symbolFromUnit = (unit) => Match.value(unit).pipe(Match.when("day", () => "d"), Match.when("week", () => "w"), Match.when("month", () => "M"), Match.when("quarter", () => "q"), Match.when("year", () => "y"), Match.exhaustive);
10
+ const failAt = (input, offset, expected) => Effect.fail(new FilterExpressionParseError({
11
+ input,
12
+ offset,
13
+ expected
14
+ }));
15
+ const isDigit = (value) => value >= "0" && value <= "9";
16
+ const isFirstPositiveDigit = (value) => value >= "1" && value <= "9";
17
+ const parseInstantExpression = Effect.fn(function* (input) {
18
+ let cursor = 0;
19
+ let expression;
20
+ let fixedAnchor = false;
21
+ if (input.startsWith("now")) {
22
+ expression = now();
23
+ cursor = 3;
24
+ } else {
25
+ const candidate = input.slice(0, 10);
26
+ if (!isIsoDate(candidate)) return yield* failAt(input, 0, "\"now\" or an ISO date (YYYY-MM-DD)");
27
+ expression = dateLiteral(candidate);
28
+ cursor = 10;
29
+ fixedAnchor = true;
30
+ }
31
+ if (fixedAnchor && cursor < input.length) {
32
+ if (input.slice(cursor, cursor + 2) !== "||") return yield* failAt(input, cursor, "\"||\" before date operations");
33
+ cursor += 2;
34
+ if (cursor === input.length) return yield* failAt(input, cursor, "an operation beginning with \"+\", \"-\", or \"/\"");
35
+ }
36
+ while (cursor < input.length) {
37
+ const operator = input[cursor];
38
+ if (operator === "/") {
39
+ const unit = unitFromSymbol(input[cursor + 1] ?? "");
40
+ if (unit === void 0) return yield* failAt(input, cursor + 1, "a date unit: d, w, M, q, or y");
41
+ expression = startOf(expression, unit);
42
+ cursor += 2;
43
+ continue;
44
+ }
45
+ if (operator !== "+" && operator !== "-") return yield* failAt(input, cursor, "an operation beginning with \"+\", \"-\", or \"/\"");
46
+ const amountStart = cursor + 1;
47
+ if (!isFirstPositiveDigit(input[amountStart] ?? "")) return yield* failAt(input, amountStart, "a positive integer without a leading zero");
48
+ cursor = amountStart + 1;
49
+ while (cursor < input.length && isDigit(input[cursor] ?? "")) cursor += 1;
50
+ const amount = Number(input.slice(amountStart, cursor));
51
+ if (!Number.isSafeInteger(amount)) return yield* failAt(input, amountStart, "a safe positive integer");
52
+ const unit = unitFromSymbol(input[cursor] ?? "");
53
+ if (unit === void 0) return yield* failAt(input, cursor, "a date unit: d, w, M, q, or y");
54
+ expression = shift(expression, operator === "+" ? amount : -amount, unit);
55
+ cursor += 1;
56
+ }
57
+ return expression;
58
+ });
59
+ const appendOperation = (base, operation) => ({
60
+ text: `${base.text}${base.fixedAnchor ? "||" : ""}${operation}`,
61
+ fixedAnchor: false
62
+ });
63
+ const formatInstantExpression = (expression) => foldInstant(normalizeInstant(expression), {
64
+ now: () => ({
65
+ text: "now",
66
+ fixedAnchor: false
67
+ }),
68
+ dateLiteral: (value) => ({
69
+ text: value,
70
+ fixedAnchor: true
71
+ }),
72
+ shift: (base, amount, unit) => appendOperation(base, `${amount < 0 ? "-" : "+"}${Math.abs(amount)}${symbolFromUnit(unit)}`),
73
+ startOf: (base, unit) => appendOperation(base, `/${symbolFromUnit(unit)}`)
74
+ }).text;
75
+ //#endregion
76
+ 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 };