Skip to content

Commit eec5eaf

Browse files
committed
πŸ”§ remove no-undef rule, using TS for that.
🎨 improve @coven/cron code and tests. 🎨 improve @coven/expression code and tests.
1 parent 62b5e1b commit eec5eaf

File tree

90 files changed

+388
-407
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

90 files changed

+388
-407
lines changed

β€Ž@coven/cron/CronString.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
/**
2-
* String cron expression.
2+
* String cron expression (5 fields separated by spaces).
33
*/
44
export type CronString = `${string} ${string} ${string} ${string} ${string}`;

β€Ž@coven/cron/README.md

+18-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,23 @@
55

66
⏳ A fantastic cron parser and constructor.
77

8+
This library is the fastest, smallest and safest cron expression parser out
9+
there. This is because it uses a regular expression (built with
10+
[`@coven/expression`](https://jsr.io/@coven/expression)) to parse strings into a
11+
consumable object, and the parse back is done by really quick curried functions
12+
and generators.
13+
14+
It also includes a `nextDate` util that given a `Date` and a valid cron
15+
expression, will return the next matching date. It does validations beforehand
16+
so no "Invalid Date" errors are returned.
17+
18+
As all [Coven Engineering](https://coven.engineering) libraries, it has 100%
19+
test coverage and it's built in top of modern tech compatible with all
20+
JavaScript runtimes.
21+
22+
Only known limitation is it only accepts valid standard unix cron expressions,
23+
so cron quartz is not supported.
24+
825
## Example
926

1027
```typescript
@@ -27,7 +44,7 @@ stringify(cron); // "1-2,3,4 * 2 8,9 1"
2744
stringify({ hours: 13 }); // "* 13 * * *"
2845

2946
// Only parses with valid dates:
30-
parse("* * 31 2 *"); // undefined because 2/31 is invalid
47+
parse("* * 31 2 *"); // undefined because 31 of February is invalid
3148
```
3249

3350
## Other links

β€Ž@coven/cron/deno.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
22
"name": "@coven/cron",
3-
"version": "0.0.5",
3+
"version": "0.1.0",
44
"exports": "./mod.ts"
55
}

β€Ž@coven/cron/isListField.ts

+11-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,16 @@
1+
import { every } from "@coven/iterables";
12
import { isArray, isNumber } from "@coven/predicates";
23
import type { Field } from "./Field.ts";
3-
import type { ListField } from "./ListField.ts";
44
import { isRangeField } from "./isRangeField.ts";
5+
import type { ListField } from "./ListField.ts";
6+
import type { ValueOrRangeField } from "./ValueOrRangeField.ts";
7+
8+
const everyIsNumberOrRangeField = every<
9+
ValueOrRangeField<number>,
10+
ValueOrRangeField<number>
11+
>((
12+
item,
13+
) => isNumber(item) || isRangeField(item));
514

615
/**
716
* Predicate checking if given value is a {@link ListField}.
@@ -11,4 +20,4 @@ import { isRangeField } from "./isRangeField.ts";
1120
*/
1221
export const isListField = (value: Field<number>): value is ListField<number> =>
1322
isArray(value) &&
14-
value.every((item) => isNumber(item) || isRangeField(item));
23+
everyIsNumberOrRangeField(value);

β€Ž@coven/cron/isListString.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { includes } from "@coven/iterables";
12
import type { ListString } from "./ListString.ts";
23
import { LIST_EXPRESSION_SEPARATOR_TOKEN } from "./tokens.ts";
34

@@ -7,5 +8,6 @@ import { LIST_EXPRESSION_SEPARATOR_TOKEN } from "./tokens.ts";
78
* @see {@link ListString}
89
* @see {@link LIST_EXPRESSION_SEPARATOR_TOKEN}
910
*/
10-
export const isListString = (value: string): value is ListString =>
11-
value.includes(LIST_EXPRESSION_SEPARATOR_TOKEN);
11+
export const isListString = includes(
12+
LIST_EXPRESSION_SEPARATOR_TOKEN,
13+
) as (value: Iterable<unknown>) => value is ListString;

β€Ž@coven/cron/isRangeField.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,18 @@ import { has, isNumber, isObject } from "@coven/predicates";
22
import type { RangeField } from "./RangeField.ts";
33
import { FROM_NAME, TO_NAME } from "./rangeFieldNames.ts";
44

5+
const hasFrom = has(FROM_NAME);
6+
const hasTo = has(TO_NAME);
7+
58
/**
69
* Predicate checking if given value is a cron object range ({@link RangeField}).
710
*
811
* @see {@link RangeField}
912
*/
1013
export const isRangeField = (value: unknown): value is RangeField<number> =>
1114
isObject(value) &&
12-
has(FROM_NAME)(value) &&
13-
has(TO_NAME)(value) &&
15+
hasFrom(value) &&
16+
hasTo(value) &&
1417
isNumber(value[FROM_NAME]) &&
1518
isNumber(value[TO_NAME]) &&
1619
value[FROM_NAME] < value[TO_NAME];

β€Ž@coven/cron/nextDates.ts

+4-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { EMPTY_STRING } from "@coven/constants";
2-
import { build, DIGIT, escape, quantity } from "@coven/expression";
2+
import { buildUnicode, DIGIT, escape, join, quantity } from "@coven/expression";
33
import { iteratorFunctionToIterableIterator } from "@coven/iterables";
44
import { isString, isUndefined } from "@coven/predicates";
55
import type { CronObject } from "./CronObject.ts";
@@ -8,6 +8,8 @@ import { dateInCron } from "./dateInCron.ts";
88
import { parse } from "./parse.ts";
99
import { stringify } from "./stringify.ts";
1010

11+
const dateReplace = join(quantity(2)(DIGIT), escape("."), quantity(3)(DIGIT));
12+
1113
/**
1214
* Get next ISO date iterator for the given date and the given cron expression.
1315
*
@@ -36,11 +38,7 @@ export const nextDates = (
3638
date
3739
.toISOString()
3840
.replace(
39-
build()(
40-
quantity(2)(DIGIT),
41-
escape("."),
42-
quantity(3)(DIGIT),
43-
),
41+
buildUnicode(dateReplace),
4442
"00.000",
4543
),
4644
);

β€Ž@coven/cron/normalizeAliases.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import type { KeyOf, ReadonlyArray } from "@coven/types";
33
import type { FieldString } from "./FieldString.ts";
44
import { normalizeMap } from "./normalizeMap.ts";
55

6+
const buildGIU = build("giu");
7+
68
/**
79
* Normalizes day and month 3 letter aliases into their number counterparts.
810
*
@@ -15,7 +17,7 @@ import { normalizeMap } from "./normalizeMap.ts";
1517
*/
1618
export const normalizeAliases = (expression: string): FieldString =>
1719
expression.replaceAll(
18-
build("giu")(
20+
buildGIU(
1921
or(
2022
...(Object.keys(normalizeMap) as ReadonlyArray<
2123
KeyOf<typeof normalizeMap>

β€Ž@coven/cron/parse.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import { cronRegExp } from "./cronRegExp.ts";
88
import { normalizeAliases } from "./normalizeAliases.ts";
99
import { parseFieldTuplesMap } from "./parseFieldTuplesMap.ts";
1010

11+
const buildIU = build("iu");
12+
1113
/**
1214
* Parses a cron expression into an object representation.
1315
*
@@ -29,7 +31,7 @@ import { parseFieldTuplesMap } from "./parseFieldTuplesMap.ts";
2931
export const parse = (expression: CronString): Maybe<CronObject> => {
3032
const entries = parseFieldTuplesMap(
3133
objectToEntries(
32-
(build("iu")(cronRegExp).exec(normalizeAliases(expression))
34+
(buildIU(cronRegExp).exec(normalizeAliases(expression))
3335
?.groups ?? EMPTY_OBJECT) as ReadonlyRecord<
3436
KeyOf<CronObject>,
3537
string

β€Ž@coven/cron/parseNumberTest.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {
2-
build,
2+
buildUnicode,
33
DIGIT,
44
END,
55
group,
@@ -27,7 +27,7 @@ import { paddedRegExp } from "./paddedRegExp.ts";
2727
* @see {@link paddedRegExp}
2828
*/
2929
export const parseNumberTest: (text: string) => boolean = test(
30-
build("u")(
30+
buildUnicode(
3131
START,
3232
group(or(paddedRegExp(DIGIT), join(set(range(1)(5)), DIGIT))),
3333
END,

β€Ž@coven/cron/rangeStringTest.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
import { build, DIGIT, END, quantity, START } from "@coven/expression";
1+
import { buildUnicode, DIGIT, END, quantity, START } from "@coven/expression";
22
import { test } from "@coven/predicates";
33
import { RANGE_EXPRESSION_SEPARATOR_TOKEN } from "./tokens.ts";
44

55
/**
66
* Regular expression to test if given string is a range.
77
*/
88
export const rangeStringTest: (text: string) => boolean = test(
9-
build("u")(
9+
buildUnicode(
1010
START,
1111
quantity("1,2")(DIGIT),
1212
RANGE_EXPRESSION_SEPARATOR_TOKEN,

β€Ž@coven/expression/build.ts

+9
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,15 @@ import { join } from "./join.ts";
1111
/**
1212
* Builds a `RegExp` with required `u` flag and strongly typed source (using
1313
* {@link join}).
14+
*
15+
* @example
16+
* ```typescript
17+
* build("ui")(group(or(13, "coven"))); // /(?:13|coven)/ui
18+
* build()(group(or(13, "coven"))); // /(?:13|coven)/u -> use the buildUnicode alias
19+
* ```
20+
* @template Flags Regular expression flags ("u" must be included always).
21+
* @param flags Regular expression flags ("u" by default).
22+
* @returns Curried function with `flags` set in context.
1423
*/
1524
export const build = <Flags extends RegularExpressionFlags = "u">(
1625
flags: Flags | "u" = "u",

β€Ž@coven/expression/buildUnicode.ts

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import type {
2+
EmptyString,
3+
ReadonlyArray,
4+
Replace,
5+
Stringable,
6+
StringJoin,
7+
} from "@coven/types";
8+
import { build } from "./build.ts";
9+
10+
/**
11+
* Builds an unicode `RegExp` (alias for `build()`).
12+
*
13+
* @example
14+
* ```typescript
15+
* buildUnicode(group(or(13, "coven"))); // /(?:13|coven)/u
16+
* ```
17+
* @param tokens String tokens to be used as the RegExp source.
18+
* @returns Regular Expression with the given tokens and the unicode flag.
19+
*/
20+
export const buildUnicode: <const Tokens extends ReadonlyArray<Stringable>>(
21+
...tokens: Tokens
22+
) => Replace<
23+
RegExp,
24+
{
25+
readonly flags: "u";
26+
readonly source: StringJoin<Tokens, EmptyString>;
27+
}
28+
> = build();

β€Ž@coven/expression/deno.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
22
"name": "@coven/expression",
3-
"version": "0.0.6",
3+
"version": "0.1.0",
44
"exports": "./mod.ts"
55
}

β€Ž@coven/expression/mod.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export { allow } from "./allow.ts";
22
export { build } from "./build.ts";
3+
export { buildUnicode } from "./buildUnicode.ts";
34
export { capture } from "./capture.ts";
45
export { capturedNumber } from "./capturedNumber.ts";
56
export { captureNamed } from "./captureNamed.ts";

β€Ž@coven/iterables/getIterator.ts

-1
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,4 @@
1414
*/
1515
export const getIterator = <Item = unknown>(
1616
iterable: Iterable<Item, unknown, undefined>,
17-
// deno-lint-ignore no-undef
1817
): IteratorObject<Item, undefined, unknown> => Iterator.from(iterable);

β€Ž@coven/iterables/objectToEntries.ts

-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ export const objectToEntries = <Key extends PropertyKey, Value>(
2020
iteratorFunctionToIterableIterator(function* (): Generator<
2121
Entry<Key extends number ? `${Key}` : Key, Value>
2222
> {
23-
// deno-lint-ignore no-undef
2423
yield* Iterator.from(Reflect.ownKeys(input)).map((key) => [
2524
key as Key extends number ? `${Key}` : Key,
2625
input[key as keyof ReadonlyRecord<Key, Value>],

β€Ždeno.json

-1
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,6 @@
113113
"no-this-before-super",
114114
"no-throw-literal",
115115
"no-top-level-await",
116-
"no-undef",
117116
"no-unreachable",
118117
"no-unsafe-finally",
119118
"no-unsafe-negation",
+16-25
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,25 @@
11
import { compareField } from "@coven/cron";
2-
import { assertStrictEquals } from "@std/assert";
2+
import { assert, assertFalse } from "@std/assert";
33

4-
Deno.test("Two equal values return true", () =>
5-
// deno-lint-ignore no-boolean-literal-for-arguments
6-
assertStrictEquals(compareField(13, 13), true));
4+
Deno.test("Two equal values returns true", () => assert(compareField(13, 13)));
75

8-
Deno.test("Two different values return false", () =>
9-
// deno-lint-ignore no-boolean-literal-for-arguments
10-
assertStrictEquals(compareField(13, 99), false));
6+
Deno.test("Two different values returns false", () =>
7+
assertFalse(compareField(13, 99)));
118

12-
Deno.test("Value and a range that contains it return true", () =>
13-
// deno-lint-ignore no-boolean-literal-for-arguments
14-
assertStrictEquals(compareField(13, { from: 0, to: 99 }), true));
9+
Deno.test("Value and a range that contains it returns true", () =>
10+
assert(compareField(13, { from: 0, to: 99 })));
1511

16-
Deno.test("Value and a range that doesn't contain it return true", () =>
17-
// deno-lint-ignore no-boolean-literal-for-arguments
18-
assertStrictEquals(compareField(13, { from: 0, to: 10 }), false));
12+
Deno.test("Value and a range that doesn't contain it returns true", () =>
13+
assertFalse(compareField(13, { from: 0, to: 10 })));
1914

20-
Deno.test("Value and a list that contains it return true", () =>
21-
// deno-lint-ignore no-boolean-literal-for-arguments
22-
assertStrictEquals(compareField(13, [10, 13]), true));
15+
Deno.test("Value and a list that contains it returns true", () =>
16+
assert(compareField(13, [10, 13])));
2317

24-
Deno.test("Value and a list that doesn't contain it return true", () =>
25-
// deno-lint-ignore no-boolean-literal-for-arguments
26-
assertStrictEquals(compareField(13, [10, 12]), false));
18+
Deno.test("Value and a list that doesn't contain it returns true", () =>
19+
assertFalse(compareField(13, [10, 12])));
2720

28-
Deno.test("Value and a list with a range that contains it return true", () =>
29-
// deno-lint-ignore no-boolean-literal-for-arguments
30-
assertStrictEquals(compareField(13, [10, { from: 11, to: 99 }]), true));
21+
Deno.test("Value and a list with a range that contains it returns true", () =>
22+
assert(compareField(13, [10, { from: 11, to: 99 }])));
3123

32-
Deno.test("Value and a list with a range that doesn't contain it return true", () =>
33-
// deno-lint-ignore no-boolean-literal-for-arguments
34-
assertStrictEquals(compareField(13, [5, { from: 10, to: 12 }]), false));
24+
Deno.test("Value and a list with a range that doesn't contain it returns true", () =>
25+
assertFalse(compareField(13, [5, { from: 10, to: 12 }])));
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,14 @@
11
import { compareRangeOrValue } from "@coven/cron";
2-
import { assertStrictEquals } from "@std/assert";
2+
import { assert, assertFalse } from "@std/assert";
33

4-
Deno.test("Two equal values return true", () =>
5-
// deno-lint-ignore no-boolean-literal-for-arguments
6-
assertStrictEquals(compareRangeOrValue(13)(13), true));
4+
Deno.test("Two equal values returns true", () =>
5+
assert(compareRangeOrValue(13)(13)));
76

8-
Deno.test("Two different values return false", () =>
9-
// deno-lint-ignore no-boolean-literal-for-arguments
10-
assertStrictEquals(compareRangeOrValue(13)(99), false));
7+
Deno.test("Two different values returns false", () =>
8+
assertFalse(compareRangeOrValue(13)(99)));
119

12-
Deno.test("Value and a range that contains it return true", () =>
13-
// deno-lint-ignore no-boolean-literal-for-arguments
14-
assertStrictEquals(compareRangeOrValue(13)({ from: 0, to: 99 }), true));
10+
Deno.test("Value and a range that contains it returns true", () =>
11+
assert(compareRangeOrValue(13)({ from: 0, to: 99 })));
1512

16-
Deno.test("Value and a range that doesn't contain it return true", () =>
17-
// deno-lint-ignore no-boolean-literal-for-arguments
18-
assertStrictEquals(compareRangeOrValue(13)({ from: 0, to: 10 }), false));
13+
Deno.test("Value and a range that doesn't contain it returns false", () =>
14+
assertFalse(compareRangeOrValue(13)({ from: 0, to: 10 })));
+5-13
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { dateInCron } from "@coven/cron";
2-
import { assertStrictEquals } from "@std/assert";
2+
import { assert, assertFalse } from "@std/assert";
33

44
const dateInCronTest = dateInCron({
55
dayOfMonth: 5,
@@ -9,16 +9,8 @@ const dateInCronTest = dateInCron({
99
month: 5,
1010
});
1111

12-
Deno.test("a date inside the cron expression return true", () =>
13-
// deno-lint-ignore no-boolean-literal-for-arguments
14-
assertStrictEquals(
15-
dateInCronTest(new Date("1989-05-05T05:05:00.000")),
16-
true,
17-
));
12+
Deno.test("Date inside the cron expression returns true", () =>
13+
assert(dateInCronTest(new Date("1989-05-05T05:05:00.000"))));
1814

19-
Deno.test("a date outside the cron expression return false", () =>
20-
// deno-lint-ignore no-boolean-literal-for-arguments
21-
assertStrictEquals(
22-
dateInCronTest(new Date("2024-05-05T05:05:00.000")),
23-
false,
24-
));
15+
Deno.test("Date outside the cron expression returns false", () =>
16+
assertFalse(dateInCronTest(new Date("2024-05-05T05:05:00.000"))));

0 commit comments

Comments
Β (0)