chronolizer 0.2.0 → 0.4.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.
@@ -1,4 +1,4 @@
1
- import { Option, Schema, String } from "effect";
1
+ import { Match, Option, Schema, String } from "effect";
2
2
  //#region src/ast/schemas.ts
3
3
  const Unit = Schema.Literals([
4
4
  "day",
@@ -8,16 +8,7 @@ const Unit = Schema.Literals([
8
8
  "year"
9
9
  ]);
10
10
  const isLeapYear = (year) => year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
11
- const daysInMonth = (year, month) => {
12
- switch (month) {
13
- case 2: return isLeapYear(year) ? 29 : 28;
14
- case 4:
15
- case 6:
16
- case 9:
17
- case 11: return 30;
18
- default: return 31;
19
- }
20
- };
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));
21
12
  const isIsoDate = (value) => {
22
13
  if (Option.isNone(String.match(/^\d{4}-\d{2}-\d{2}$/)(value))) return false;
23
14
  const year = Number(value.slice(0, 4));
@@ -26,21 +17,21 @@ const isIsoDate = (value) => {
26
17
  return month >= 1 && month <= 12 && day >= 1 && day <= daysInMonth(year, month);
27
18
  };
28
19
  const IsoDate = Schema.String.check(Schema.makeFilter(isIsoDate, { expected: "an ISO calendar date (YYYY-MM-DD)" })).annotate({ identifier: "IsoDate" });
29
- const Now = Schema.TaggedStruct("Now", {});
30
- const DateLiteral = Schema.TaggedStruct("DateLiteral", { value: IsoDate });
20
+ var Now = class extends Schema.TaggedClass()("Now", {}) {};
21
+ var DateLiteral = class extends Schema.TaggedClass()("DateLiteral", { value: IsoDate }) {};
31
22
  const ShiftAmount = Schema.Int.check(Schema.isBetween({
32
23
  minimum: Number.MIN_SAFE_INTEGER,
33
24
  maximum: Number.MAX_SAFE_INTEGER
34
25
  }));
35
- const Shift = Schema.TaggedStruct("Shift", {
26
+ var Shift = class extends Schema.TaggedClass()("Shift", {
36
27
  base: Schema.suspend(() => InstantExpr),
37
28
  amount: ShiftAmount,
38
29
  unit: Unit
39
- });
40
- const StartOf = Schema.TaggedStruct("StartOf", {
30
+ }) {};
31
+ var StartOf = class extends Schema.TaggedClass()("StartOf", {
41
32
  base: Schema.suspend(() => InstantExpr),
42
33
  unit: Unit
43
- });
34
+ }) {};
44
35
  const InstantExpr = Schema.suspend(() => Schema.Union([
45
36
  Now,
46
37
  DateLiteral,
@@ -3,27 +3,10 @@ import { dateLiteral, now, shift, startOf } from "../ast/constructors.mjs";
3
3
  import { foldInstant } from "../ast/fold.mjs";
4
4
  import { normalizeInstant } from "../ast/normalize.mjs";
5
5
  import { FilterExpressionParseError } from "./errors.mjs";
6
- import { Effect } from "effect";
6
+ import { Effect, Match } from "effect";
7
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
- };
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);
27
10
  const failAt = (input, offset, expected) => Effect.fail(new FilterExpressionParseError({
28
11
  input,
29
12
  offset,
@@ -31,25 +14,31 @@ const failAt = (input, offset, expected) => Effect.fail(new FilterExpressionPars
31
14
  }));
32
15
  const isDigit = (value) => value >= "0" && value <= "9";
33
16
  const isFirstPositiveDigit = (value) => value >= "1" && value <= "9";
17
+ const parseAnchor = Effect.fn(function* (input) {
18
+ if (input.startsWith("now")) return {
19
+ expression: now(),
20
+ cursor: 3,
21
+ fixed: false
22
+ };
23
+ const candidate = input.slice(0, 10);
24
+ if (!isIsoDate(candidate)) return yield* failAt(input, 0, "\"now\" or an ISO date (YYYY-MM-DD)");
25
+ return {
26
+ expression: dateLiteral(candidate),
27
+ cursor: 10,
28
+ fixed: true
29
+ };
30
+ });
31
+ const operationStart = Effect.fn(function* (input, anchor) {
32
+ if (!anchor.fixed || anchor.cursor === input.length) return anchor.cursor;
33
+ if (input.slice(anchor.cursor, anchor.cursor + 2) !== "||") return yield* failAt(input, anchor.cursor, "\"||\" before date operations");
34
+ const cursor = anchor.cursor + 2;
35
+ if (cursor === input.length) return yield* failAt(input, cursor, "an operation beginning with \"+\", \"-\", or \"/\"");
36
+ return cursor;
37
+ });
34
38
  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
- }
39
+ const anchor = yield* parseAnchor(input);
40
+ let expression = anchor.expression;
41
+ let cursor = yield* operationStart(input, anchor);
53
42
  while (cursor < input.length) {
54
43
  const operator = input[cursor];
55
44
  if (operator === "/") {
package/dist/index.d.mts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { DateLiteral, DateRangeExpr, GreaterThan, GreaterThanOrEqual, InstantExpr, IsoDate, LessThan, LessThanOrEqual, LowerBound, Now, Shift, StartOf, Unit, UpperBound, daysInMonth, isIsoDate } from "./ast/schemas.mjs";
2
2
  import { boundedRange, dateLiteral, greaterThan, greaterThanOrEqual, lessThan, lessThanOrEqual, lowerOpenRange, now, shift, startOf, upperOpenRange } from "./ast/constructors.mjs";
3
- import { InstantAlgebra, containsPositiveShift, foldInstant } from "./ast/fold.mjs";
3
+ import { InstantAlgebra, containsPositiveShift, foldInstant, isCurrentPeriod } from "./ast/fold.mjs";
4
4
  import { normalizeInstant, normalizeRange } from "./ast/normalize.mjs";
5
5
  import { FilterExpressionParseError, InvalidDateFilterError } from "./filter/errors.mjs";
6
6
  import { completePeriod, formatFilter, parseFilter, rangeKey } from "./filter/codec.mjs";
@@ -19,4 +19,4 @@ import { SuggestNaturalOptions, suggestNatural } from "./natural/suggest.mjs";
19
19
  import { naturalWords, normalizeNaturalText } from "./natural/text.mjs";
20
20
  import { ResolutionError, ResolvedDateRange, ResolvedGreaterThan, ResolvedGreaterThanOrEqual, ResolvedLessThan, ResolvedLessThanOrEqual, ResolvedLowerBound, ResolvedUpperBound } from "./resolve/schema.mjs";
21
21
  import { resolve } from "./resolve/resolve.mjs";
22
- export { AmbiguousNaturalLanguageError, BaseLanguageContribution, BaseLanguageMetadata, CompiledLanguage, Correction, DateExpressionString, DateFilter, DateLiteral, DateRangeExpr, DateRangeFromFilter, EnglishContribution, EnglishLanguage, EnglishLanguageLayer, FilterExpressionParseError, type FormatNaturalOptions, GreaterThan, GreaterThanOrEqual, InstantAlgebra, InstantExpr, InstantExpressionFromString, InvalidDateFilterError, IsoDate, LanguageConflictError, LanguageContribution, LanguageContributionMetadata, LanguageExtensionContribution, LanguageExtensionMetadata, LanguagePlugin, LanguagePluginContext, LanguageRegistrationError, LanguageRegistry, LanguageRegistryLayer, LessThan, LessThanOrEqual, Locale, LowerBound, NaturalAlternative, NaturalCandidate, NaturalCorrectionCandidate, NaturalLanguageParseError, NaturalLanguageRenderError, NaturalParseResult, NaturalSuggestion, Now, type ParseNaturalOptions, ParseQuality, ResolutionError, ResolvedDateRange, ResolvedGreaterThan, ResolvedGreaterThanOrEqual, ResolvedLessThan, ResolvedLessThanOrEqual, ResolvedLowerBound, ResolvedUpperBound, Shift, StartOf, type SuggestNaturalOptions, Unit, UnsupportedLocaleError, UpperBound, boundedRange, canonicalBaseLocale, completeNaturalPhrases, completePeriod, containsPositiveShift, correctWhitespaceSeparatedText, dateLiteral, daysInMonth, defineLanguagePlugin, foldInstant, formatFilter, formatInstantExpression, formatNatural, greaterThan, greaterThanOrEqual, isIsoDate, languagePluginsLayer, lessThan, lessThanOrEqual, lowerOpenRange, naturalWords, normalizeInstant, normalizeNaturalText, normalizeRange, now, parseFilter, parseInstantExpression, parseNatural, rangeKey, resolve, shift, startOf, suggestNatural, upperOpenRange };
22
+ export { AmbiguousNaturalLanguageError, BaseLanguageContribution, BaseLanguageMetadata, CompiledLanguage, Correction, DateExpressionString, DateFilter, DateLiteral, DateRangeExpr, DateRangeFromFilter, EnglishContribution, EnglishLanguage, EnglishLanguageLayer, FilterExpressionParseError, type FormatNaturalOptions, GreaterThan, GreaterThanOrEqual, InstantAlgebra, InstantExpr, InstantExpressionFromString, InvalidDateFilterError, IsoDate, LanguageConflictError, LanguageContribution, LanguageContributionMetadata, LanguageExtensionContribution, LanguageExtensionMetadata, LanguagePlugin, LanguagePluginContext, LanguageRegistrationError, LanguageRegistry, LanguageRegistryLayer, LessThan, LessThanOrEqual, Locale, LowerBound, NaturalAlternative, NaturalCandidate, NaturalCorrectionCandidate, NaturalLanguageParseError, NaturalLanguageRenderError, NaturalParseResult, NaturalSuggestion, Now, type ParseNaturalOptions, ParseQuality, ResolutionError, ResolvedDateRange, ResolvedGreaterThan, ResolvedGreaterThanOrEqual, ResolvedLessThan, ResolvedLessThanOrEqual, ResolvedLowerBound, ResolvedUpperBound, Shift, StartOf, type SuggestNaturalOptions, Unit, UnsupportedLocaleError, UpperBound, boundedRange, canonicalBaseLocale, completeNaturalPhrases, completePeriod, containsPositiveShift, correctWhitespaceSeparatedText, dateLiteral, daysInMonth, defineLanguagePlugin, foldInstant, formatFilter, formatInstantExpression, formatNatural, greaterThan, greaterThanOrEqual, isCurrentPeriod, isIsoDate, languagePluginsLayer, lessThan, lessThanOrEqual, lowerOpenRange, naturalWords, normalizeInstant, normalizeNaturalText, normalizeRange, now, parseFilter, parseInstantExpression, parseNatural, rangeKey, resolve, shift, startOf, suggestNatural, upperOpenRange };
package/dist/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { DateLiteral, DateRangeExpr, GreaterThan, GreaterThanOrEqual, InstantExpr, IsoDate, LessThan, LessThanOrEqual, LowerBound, Now, Shift, StartOf, Unit, UpperBound, daysInMonth, isIsoDate } from "./ast/schemas.mjs";
2
2
  import { boundedRange, dateLiteral, greaterThan, greaterThanOrEqual, lessThan, lessThanOrEqual, lowerOpenRange, now, shift, startOf, upperOpenRange } from "./ast/constructors.mjs";
3
- import { containsPositiveShift, foldInstant } from "./ast/fold.mjs";
3
+ import { containsPositiveShift, foldInstant, isCurrentPeriod } from "./ast/fold.mjs";
4
4
  import { normalizeInstant, normalizeRange } from "./ast/normalize.mjs";
5
5
  import { FilterExpressionParseError, InvalidDateFilterError } from "./filter/errors.mjs";
6
6
  import { formatInstantExpression, parseInstantExpression } from "./filter/expression.mjs";
@@ -19,4 +19,4 @@ import { parseNatural } from "./natural/parse.mjs";
19
19
  import { suggestNatural } from "./natural/suggest.mjs";
20
20
  import { ResolutionError, ResolvedDateRange, ResolvedGreaterThan, ResolvedGreaterThanOrEqual, ResolvedLessThan, ResolvedLessThanOrEqual, ResolvedLowerBound, ResolvedUpperBound } from "./resolve/schema.mjs";
21
21
  import { resolve } from "./resolve/resolve.mjs";
22
- export { AmbiguousNaturalLanguageError, BaseLanguageContribution, BaseLanguageMetadata, Correction, DateExpressionString, DateFilter, DateLiteral, DateRangeExpr, DateRangeFromFilter, EnglishContribution, EnglishLanguage, EnglishLanguageLayer, FilterExpressionParseError, GreaterThan, GreaterThanOrEqual, InstantExpr, InstantExpressionFromString, InvalidDateFilterError, IsoDate, LanguageConflictError, LanguageContributionMetadata, LanguageExtensionContribution, LanguageExtensionMetadata, LanguageRegistrationError, LanguageRegistry, LanguageRegistryLayer, LessThan, LessThanOrEqual, Locale, LowerBound, NaturalAlternative, NaturalCandidate, NaturalCorrectionCandidate, NaturalLanguageParseError, NaturalLanguageRenderError, NaturalParseResult, NaturalSuggestion, Now, ParseQuality, ResolutionError, ResolvedDateRange, ResolvedGreaterThan, ResolvedGreaterThanOrEqual, ResolvedLessThan, ResolvedLessThanOrEqual, ResolvedLowerBound, ResolvedUpperBound, Shift, StartOf, Unit, UnsupportedLocaleError, UpperBound, boundedRange, canonicalBaseLocale, completeNaturalPhrases, completePeriod, containsPositiveShift, correctWhitespaceSeparatedText, dateLiteral, daysInMonth, defineLanguagePlugin, foldInstant, formatFilter, formatInstantExpression, formatNatural, greaterThan, greaterThanOrEqual, isIsoDate, languagePluginsLayer, lessThan, lessThanOrEqual, lowerOpenRange, naturalWords, normalizeInstant, normalizeNaturalText, normalizeRange, now, parseFilter, parseInstantExpression, parseNatural, rangeKey, resolve, shift, startOf, suggestNatural, upperOpenRange };
22
+ export { AmbiguousNaturalLanguageError, BaseLanguageContribution, BaseLanguageMetadata, Correction, DateExpressionString, DateFilter, DateLiteral, DateRangeExpr, DateRangeFromFilter, EnglishContribution, EnglishLanguage, EnglishLanguageLayer, FilterExpressionParseError, GreaterThan, GreaterThanOrEqual, InstantExpr, InstantExpressionFromString, InvalidDateFilterError, IsoDate, LanguageConflictError, LanguageContributionMetadata, LanguageExtensionContribution, LanguageExtensionMetadata, LanguageRegistrationError, LanguageRegistry, LanguageRegistryLayer, LessThan, LessThanOrEqual, Locale, LowerBound, NaturalAlternative, NaturalCandidate, NaturalCorrectionCandidate, NaturalLanguageParseError, NaturalLanguageRenderError, NaturalParseResult, NaturalSuggestion, Now, ParseQuality, ResolutionError, ResolvedDateRange, ResolvedGreaterThan, ResolvedGreaterThanOrEqual, ResolvedLessThan, ResolvedLessThanOrEqual, ResolvedLowerBound, ResolvedUpperBound, Shift, StartOf, Unit, UnsupportedLocaleError, UpperBound, boundedRange, canonicalBaseLocale, completeNaturalPhrases, completePeriod, containsPositiveShift, correctWhitespaceSeparatedText, dateLiteral, daysInMonth, defineLanguagePlugin, foldInstant, formatFilter, formatInstantExpression, formatNatural, greaterThan, greaterThanOrEqual, isCurrentPeriod, isIsoDate, languagePluginsLayer, lessThan, lessThanOrEqual, lowerOpenRange, naturalWords, normalizeInstant, normalizeNaturalText, normalizeRange, now, parseFilter, parseInstantExpression, parseNatural, rangeKey, resolve, shift, startOf, suggestNatural, upperOpenRange };
@@ -22,8 +22,37 @@ declare const NaturalCorrectionCandidate: Schema.Struct<{
22
22
  readonly cost: Schema.Int;
23
23
  }>;
24
24
  type NaturalCorrectionCandidate = typeof NaturalCorrectionCandidate.Type;
25
- declare const NaturalAlternative: Schema.Struct<{
25
+ declare const NaturalCandidate: Schema.Struct<{
26
+ readonly range: Schema.Union<readonly [Schema.TaggedStruct<"DateRange", {
27
+ readonly lower: Schema.Union<readonly [Schema.TaggedStruct<"GreaterThan", {
28
+ readonly value: Schema.Codec<InstantExpr, InstantExpr, never, never>;
29
+ }>, Schema.TaggedStruct<"GreaterThanOrEqual", {
30
+ readonly value: Schema.Codec<InstantExpr, InstantExpr, never, never>;
31
+ }>]>;
32
+ readonly upper: Schema.Union<readonly [Schema.TaggedStruct<"LessThan", {
33
+ readonly value: Schema.Codec<InstantExpr, InstantExpr, never, never>;
34
+ }>, Schema.TaggedStruct<"LessThanOrEqual", {
35
+ readonly value: Schema.Codec<InstantExpr, InstantExpr, never, never>;
36
+ }>]>;
37
+ }>, Schema.TaggedStruct<"DateRange", {
38
+ readonly lower: Schema.Union<readonly [Schema.TaggedStruct<"GreaterThan", {
39
+ readonly value: Schema.Codec<InstantExpr, InstantExpr, never, never>;
40
+ }>, Schema.TaggedStruct<"GreaterThanOrEqual", {
41
+ readonly value: Schema.Codec<InstantExpr, InstantExpr, never, never>;
42
+ }>]>;
43
+ readonly upper: Schema.optionalKey<Schema.Never>;
44
+ }>, Schema.TaggedStruct<"DateRange", {
45
+ readonly lower: Schema.optionalKey<Schema.Never>;
46
+ readonly upper: 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
+ }>]>;
26
52
  readonly canonical: Schema.String;
53
+ }>;
54
+ type NaturalCandidate = typeof NaturalCandidate.Type;
55
+ declare const NaturalAlternative: Schema.Struct<{
27
56
  readonly range: Schema.Union<readonly [Schema.TaggedStruct<"DateRange", {
28
57
  readonly lower: Schema.Union<readonly [Schema.TaggedStruct<"GreaterThan", {
29
58
  readonly value: Schema.Codec<InstantExpr, InstantExpr, never, never>;
@@ -50,8 +79,9 @@ declare const NaturalAlternative: Schema.Struct<{
50
79
  readonly value: Schema.Codec<InstantExpr, InstantExpr, never, never>;
51
80
  }>]>;
52
81
  }>]>;
82
+ readonly canonical: Schema.String;
53
83
  }>;
54
- type NaturalAlternative = typeof NaturalAlternative.Type;
84
+ type NaturalAlternative = NaturalCandidate;
55
85
  declare const NaturalParseResult: Schema.Struct<{
56
86
  readonly range: Schema.Union<readonly [Schema.TaggedStruct<"DateRange", {
57
87
  readonly lower: Schema.Union<readonly [Schema.TaggedStruct<"GreaterThan", {
@@ -87,7 +117,6 @@ declare const NaturalParseResult: Schema.Struct<{
87
117
  readonly offset: Schema.Int;
88
118
  }>>;
89
119
  readonly alternatives: Schema.$Array<Schema.Struct<{
90
- readonly canonical: Schema.String;
91
120
  readonly range: Schema.Union<readonly [Schema.TaggedStruct<"DateRange", {
92
121
  readonly lower: Schema.Union<readonly [Schema.TaggedStruct<"GreaterThan", {
93
122
  readonly value: Schema.Codec<InstantExpr, InstantExpr, never, never>;
@@ -114,39 +143,10 @@ declare const NaturalParseResult: Schema.Struct<{
114
143
  readonly value: Schema.Codec<InstantExpr, InstantExpr, never, never>;
115
144
  }>]>;
116
145
  }>]>;
146
+ readonly canonical: Schema.String;
117
147
  }>>;
118
148
  }>;
119
149
  type NaturalParseResult = typeof NaturalParseResult.Type;
120
- declare const NaturalCandidate: Schema.Struct<{
121
- readonly range: Schema.Union<readonly [Schema.TaggedStruct<"DateRange", {
122
- readonly lower: Schema.Union<readonly [Schema.TaggedStruct<"GreaterThan", {
123
- readonly value: Schema.Codec<InstantExpr, InstantExpr, never, never>;
124
- }>, Schema.TaggedStruct<"GreaterThanOrEqual", {
125
- readonly value: Schema.Codec<InstantExpr, InstantExpr, never, never>;
126
- }>]>;
127
- readonly upper: Schema.Union<readonly [Schema.TaggedStruct<"LessThan", {
128
- readonly value: Schema.Codec<InstantExpr, InstantExpr, never, never>;
129
- }>, Schema.TaggedStruct<"LessThanOrEqual", {
130
- readonly value: Schema.Codec<InstantExpr, InstantExpr, never, never>;
131
- }>]>;
132
- }>, Schema.TaggedStruct<"DateRange", {
133
- readonly lower: Schema.Union<readonly [Schema.TaggedStruct<"GreaterThan", {
134
- readonly value: Schema.Codec<InstantExpr, InstantExpr, never, never>;
135
- }>, Schema.TaggedStruct<"GreaterThanOrEqual", {
136
- readonly value: Schema.Codec<InstantExpr, InstantExpr, never, never>;
137
- }>]>;
138
- readonly upper: Schema.optionalKey<Schema.Never>;
139
- }>, Schema.TaggedStruct<"DateRange", {
140
- readonly lower: Schema.optionalKey<Schema.Never>;
141
- readonly upper: Schema.Union<readonly [Schema.TaggedStruct<"LessThan", {
142
- readonly value: Schema.Codec<InstantExpr, InstantExpr, never, never>;
143
- }>, Schema.TaggedStruct<"LessThanOrEqual", {
144
- readonly value: Schema.Codec<InstantExpr, InstantExpr, never, never>;
145
- }>]>;
146
- }>]>;
147
- readonly canonical: Schema.String;
148
- }>;
149
- type NaturalCandidate = typeof NaturalCandidate.Type;
150
150
  declare const NaturalSuggestion: Schema.Struct<{
151
151
  readonly text: Schema.String;
152
152
  readonly range: Schema.Union<readonly [Schema.TaggedStruct<"DateRange", {
@@ -17,20 +17,17 @@ const NaturalCorrectionCandidate = Schema.Struct({
17
17
  corrections: Schema.Array(Correction),
18
18
  cost: Schema.Int.check(Schema.isGreaterThanOrEqualTo(1))
19
19
  });
20
- const NaturalAlternative = Schema.Struct({
21
- canonical: Schema.String,
22
- range: DateRangeExpr
20
+ const NaturalCandidate = Schema.Struct({
21
+ range: DateRangeExpr,
22
+ canonical: Schema.String
23
23
  });
24
+ const NaturalAlternative = NaturalCandidate;
24
25
  const NaturalParseResult = Schema.Struct({
25
26
  range: DateRangeExpr,
26
27
  quality: ParseQuality,
27
28
  corrections: Schema.Array(Correction),
28
29
  alternatives: Schema.Array(NaturalAlternative)
29
30
  });
30
- const NaturalCandidate = Schema.Struct({
31
- range: DateRangeExpr,
32
- canonical: Schema.String
33
- });
34
31
  const NaturalSuggestion = Schema.Struct({
35
32
  text: Schema.String,
36
33
  range: DateRangeExpr
@@ -38,7 +35,7 @@ const NaturalSuggestion = Schema.Struct({
38
35
  var BaseLanguageContribution = class extends Data.TaggedClass("BaseLanguage") {};
39
36
  var LanguageExtensionContribution = class extends Data.TaggedClass("LanguageExtension") {};
40
37
  const canonicalBaseLocale = (input) => Result.getSuccess(Result.try(() => new Intl.Locale(input).baseName));
41
- const Locale = Schema.String.check(Schema.makeFilter((value) => Option.contains(canonicalBaseLocale(value), value), { expected: "a canonical BCP 47 base locale identifier" })).annotate({ identifier: "Locale" });
38
+ const Locale = Schema.String.check(Schema.makeFilter((value) => Option.contains(canonicalBaseLocale(value), value), { expected: "a standard BCP 47 language code" })).annotate({ identifier: "Locale" });
42
39
  const BaseLanguageMetadata = Schema.TaggedStruct("BaseLanguage", {
43
40
  locale: Locale,
44
41
  vocabulary: Schema.Array(Schema.String)
@@ -1,20 +1,9 @@
1
1
  import { LanguageConflictError, LanguageRegistrationError, UnsupportedLocaleError } from "./errors.mjs";
2
- import { BaseLanguageMetadata, LanguageContributionMetadata, LanguageExtensionMetadata, canonicalBaseLocale } from "./model.mjs";
2
+ import { LanguageContributionMetadata, canonicalBaseLocale } from "./model.mjs";
3
3
  import { normalizeNaturalText } from "../natural/text.mjs";
4
4
  import { Array, Context, Effect, Layer, Match, Option, Order, Ref, Result, Schema } from "effect";
5
5
  //#region src/language/registry.ts
6
6
  var LanguageRegistry = class extends Context.Service()("chronolizer/LanguageRegistry") {};
7
- const metadataOf = (contribution) => Match.valueTags(contribution, {
8
- BaseLanguage: (base) => BaseLanguageMetadata.make({
9
- locale: base.locale,
10
- vocabulary: base.vocabulary
11
- }),
12
- LanguageExtension: (extension) => LanguageExtensionMetadata.make({
13
- locale: extension.locale,
14
- priority: extension.priority,
15
- vocabulary: extension.vocabulary
16
- })
17
- });
18
7
  const localeCandidates = (locale) => {
19
8
  const candidates = [locale];
20
9
  let parent = locale;
@@ -44,14 +33,7 @@ const compileLanguage = (locale, registered) => {
44
33
  const vocabulary = Object.freeze(Array.dedupe([...base.contribution.vocabulary, ...Array.flatMap(extensions, (entry) => entry.contribution.vocabulary)]));
45
34
  const parsers = Object.freeze([...extensions.map((entry) => entry.contribution.parseExact), base.contribution.parseExact]);
46
35
  const suggesters = Object.freeze([...extensions.map((entry) => entry.contribution.suggest), base.contribution.suggest].filter((suggest) => suggest !== void 0));
47
- const parseExact = (input) => {
48
- const candidates = [];
49
- for (const parser of parsers) {
50
- const candidate = parser(input);
51
- if (Option.isSome(candidate)) candidates.push(candidate.value);
52
- }
53
- return candidates;
54
- };
36
+ const parseExact = (input) => Array.flatMap(parsers, (parser) => Option.toArray(parser(input)));
55
37
  return Option.some(Object.freeze({
56
38
  locale: base.contribution.locale,
57
39
  vocabulary,
@@ -63,28 +45,34 @@ const compileLanguage = (locale, registered) => {
63
45
  }));
64
46
  };
65
47
  const createRegistry = Effect.fn(function* () {
66
- const entries = yield* Ref.make([]);
48
+ const state = yield* Ref.make({
49
+ entries: [],
50
+ compiledLanguages: /* @__PURE__ */ new Map()
51
+ });
67
52
  const register = Effect.fn(function* (pluginId, contribution) {
68
- if (pluginId.length === 0 || !Schema.is(LanguageContributionMetadata)(metadataOf(contribution))) return yield* new LanguageRegistrationError({
53
+ if (pluginId.length === 0 || !Schema.is(LanguageContributionMetadata)(contribution)) return yield* new LanguageRegistrationError({
69
54
  pluginId,
70
55
  locale: contribution.locale,
71
- message: "Invalid plugin identifier or contribution metadata"
56
+ message: "The plugin name or language settings are invalid"
72
57
  });
73
58
  const token = Symbol(pluginId);
74
- yield* Effect.acquireRelease(Ref.modify(entries, (current) => {
59
+ yield* Effect.acquireRelease(Ref.modify(state, (current) => {
75
60
  const conflictingBase = Match.valueTags(contribution, {
76
- BaseLanguage: (base) => Array.findFirst(current, (entry) => Match.valueTags(entry.contribution, {
61
+ BaseLanguage: (base) => Array.findFirst(current.entries, (entry) => Match.valueTags(entry.contribution, {
77
62
  BaseLanguage: (registeredBase) => registeredBase.locale === base.locale,
78
63
  LanguageExtension: () => false
79
64
  })),
80
65
  LanguageExtension: () => Option.none()
81
66
  });
82
67
  return Option.match(conflictingBase, {
83
- onNone: () => [Result.succeed(token), Array.append(current, {
84
- token,
85
- pluginId,
86
- contribution
87
- })],
68
+ onNone: () => [Result.succeed(token), {
69
+ entries: Array.append(current.entries, {
70
+ token,
71
+ pluginId,
72
+ contribution
73
+ }),
74
+ compiledLanguages: /* @__PURE__ */ new Map()
75
+ }],
88
76
  onSome: (conflict) => [Result.fail(new LanguageConflictError({
89
77
  locale: contribution.locale,
90
78
  firstPluginId: conflict.pluginId,
@@ -92,12 +80,26 @@ const createRegistry = Effect.fn(function* () {
92
80
  message: "Only one base language can be registered for a locale"
93
81
  })), current]
94
82
  });
95
- }).pipe(Effect.flatMap((result) => Effect.fromResult(result))), (registeredToken) => Ref.update(entries, (items) => Array.filter(items, (entry) => entry.token !== registeredToken)));
83
+ }).pipe(Effect.flatMap((result) => Effect.fromResult(result))), (registeredToken) => Ref.update(state, (current) => ({
84
+ entries: Array.filter(current.entries, (entry) => entry.token !== registeredToken),
85
+ compiledLanguages: /* @__PURE__ */ new Map()
86
+ })));
96
87
  });
97
88
  const resolve = Effect.fn(function* (locale) {
98
89
  const canonical = canonicalBaseLocale(locale);
99
90
  if (Option.isSome(canonical)) {
100
- const compiled = compileLanguage(canonical.value, yield* Ref.get(entries));
91
+ const compiled = yield* Ref.modify(state, (current) => {
92
+ const cached = current.compiledLanguages.get(canonical.value);
93
+ if (cached !== void 0) return [Option.some(cached), current];
94
+ const language = compileLanguage(canonical.value, current.entries);
95
+ if (Option.isNone(language)) return [language, current];
96
+ const cache = new Map(current.compiledLanguages);
97
+ cache.set(canonical.value, language.value);
98
+ return [language, {
99
+ entries: current.entries,
100
+ compiledLanguages: cache
101
+ }];
102
+ });
101
103
  if (Option.isSome(compiled)) return compiled.value;
102
104
  }
103
105
  return yield* new UnsupportedLocaleError({ locale });
@@ -120,7 +122,7 @@ const createPluginRegistry = Effect.fn(function* (plugins) {
120
122
  if (duplicate !== void 0) return yield* new LanguageRegistrationError({
121
123
  pluginId: duplicate,
122
124
  locale: "*",
123
- message: "Plugin identifiers must be unique"
125
+ message: "Plugin names must be unique"
124
126
  });
125
127
  const registry = yield* createRegistry();
126
128
  const context = { register: registry.register };