effect 2.0.1 → 2.0.3
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/Cron/package.json +6 -0
- package/dist/cjs/Cron.js +457 -0
- package/dist/cjs/Cron.js.map +1 -0
- package/dist/cjs/Fiber.js.map +1 -1
- package/dist/cjs/ReadonlyArray.js +13 -7
- package/dist/cjs/ReadonlyArray.js.map +1 -1
- package/dist/cjs/Schedule.js +12 -1
- package/dist/cjs/Schedule.js.map +1 -1
- package/dist/cjs/index.js +4 -2
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/internal/schedule.js +29 -2
- package/dist/cjs/internal/schedule.js.map +1 -1
- package/dist/cjs/internal/version.js +1 -1
- package/dist/dts/Cron.d.ts +170 -0
- package/dist/dts/Cron.d.ts.map +1 -0
- package/dist/dts/Fiber.d.ts +6 -0
- package/dist/dts/Fiber.d.ts.map +1 -1
- package/dist/dts/ReadonlyArray.d.ts +26 -26
- package/dist/dts/ReadonlyArray.d.ts.map +1 -1
- package/dist/dts/Schedule.d.ts +11 -0
- package/dist/dts/Schedule.d.ts.map +1 -1
- package/dist/dts/index.d.ts +4 -0
- package/dist/dts/index.d.ts.map +1 -1
- package/dist/dts/internal/version.d.ts +1 -1
- package/dist/esm/Cron.js +418 -0
- package/dist/esm/Cron.js.map +1 -0
- package/dist/esm/Fiber.js.map +1 -1
- package/dist/esm/ReadonlyArray.js +14 -8
- package/dist/esm/ReadonlyArray.js.map +1 -1
- package/dist/esm/Schedule.js +11 -0
- package/dist/esm/Schedule.js.map +1 -1
- package/dist/esm/index.js +4 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/internal/schedule.js +26 -0
- package/dist/esm/internal/schedule.js.map +1 -1
- package/dist/esm/internal/version.js +1 -1
- package/package.json +9 -1
- package/src/Cron.ts +525 -0
- package/src/Fiber.ts +7 -0
- package/src/ReadonlyArray.ts +74 -53
- package/src/Schedule.ts +12 -0
- package/src/index.ts +5 -0
- package/src/internal/schedule.ts +42 -0
- package/src/internal/version.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "effect",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.3",
|
|
4
4
|
"description": "Functional programming in TypeScript",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -98,6 +98,11 @@
|
|
|
98
98
|
"import": "./dist/esm/Context.js",
|
|
99
99
|
"default": "./dist/cjs/Context.js"
|
|
100
100
|
},
|
|
101
|
+
"./Cron": {
|
|
102
|
+
"types": "./dist/dts/Cron.d.ts",
|
|
103
|
+
"import": "./dist/esm/Cron.js",
|
|
104
|
+
"default": "./dist/cjs/Cron.js"
|
|
105
|
+
},
|
|
101
106
|
"./Data": {
|
|
102
107
|
"types": "./dist/dts/Data.d.ts",
|
|
103
108
|
"import": "./dist/esm/Data.js",
|
|
@@ -789,6 +794,9 @@
|
|
|
789
794
|
"Context": [
|
|
790
795
|
"./dist/dts/Context.d.ts"
|
|
791
796
|
],
|
|
797
|
+
"Cron": [
|
|
798
|
+
"./dist/dts/Cron.d.ts"
|
|
799
|
+
],
|
|
792
800
|
"Data": [
|
|
793
801
|
"./dist/dts/Data.d.ts"
|
|
794
802
|
],
|
package/src/Cron.ts
ADDED
|
@@ -0,0 +1,525 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @since 2.0.0
|
|
3
|
+
*/
|
|
4
|
+
import * as Either from "./Either.js"
|
|
5
|
+
import * as Equal from "./Equal.js"
|
|
6
|
+
import * as equivalence from "./Equivalence.js"
|
|
7
|
+
import { dual, pipe } from "./Function.js"
|
|
8
|
+
import * as Hash from "./Hash.js"
|
|
9
|
+
import { format, type Inspectable, NodeInspectSymbol } from "./Inspectable.js"
|
|
10
|
+
import * as N from "./Number.js"
|
|
11
|
+
import { type Pipeable, pipeArguments } from "./Pipeable.js"
|
|
12
|
+
import { hasProperty } from "./Predicate.js"
|
|
13
|
+
import * as ReadonlyArray from "./ReadonlyArray.js"
|
|
14
|
+
import * as String from "./String.js"
|
|
15
|
+
import type { Mutable } from "./Types.js"
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @since 2.0.0
|
|
19
|
+
* @category symbols
|
|
20
|
+
*/
|
|
21
|
+
export const TypeId: unique symbol = Symbol.for("effect/Cron")
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @since 2.0.0
|
|
25
|
+
* @category symbol
|
|
26
|
+
*/
|
|
27
|
+
export type TypeId = typeof TypeId
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @since 2.0.0
|
|
31
|
+
* @category models
|
|
32
|
+
*/
|
|
33
|
+
export interface Cron extends Pipeable, Equal.Equal, Inspectable {
|
|
34
|
+
readonly [TypeId]: TypeId
|
|
35
|
+
readonly minutes: ReadonlySet<number>
|
|
36
|
+
readonly hours: ReadonlySet<number>
|
|
37
|
+
readonly days: ReadonlySet<number>
|
|
38
|
+
readonly months: ReadonlySet<number>
|
|
39
|
+
readonly weekdays: ReadonlySet<number>
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const CronProto: Omit<Cron, "minutes" | "hours" | "days" | "months" | "weekdays"> = {
|
|
43
|
+
[TypeId]: TypeId,
|
|
44
|
+
[Equal.symbol](this: Cron, that: unknown) {
|
|
45
|
+
return isCron(that) && equals(this, that)
|
|
46
|
+
},
|
|
47
|
+
[Hash.symbol](this: Cron): number {
|
|
48
|
+
return pipe(
|
|
49
|
+
Hash.array(ReadonlyArray.fromIterable(this.minutes)),
|
|
50
|
+
Hash.combine(Hash.array(ReadonlyArray.fromIterable(this.hours))),
|
|
51
|
+
Hash.combine(Hash.array(ReadonlyArray.fromIterable(this.days))),
|
|
52
|
+
Hash.combine(Hash.array(ReadonlyArray.fromIterable(this.months))),
|
|
53
|
+
Hash.combine(Hash.array(ReadonlyArray.fromIterable(this.weekdays)))
|
|
54
|
+
)
|
|
55
|
+
},
|
|
56
|
+
toString(this: Cron) {
|
|
57
|
+
return format(this.toJSON())
|
|
58
|
+
},
|
|
59
|
+
toJSON(this: Cron) {
|
|
60
|
+
return {
|
|
61
|
+
_id: "Cron",
|
|
62
|
+
minutes: ReadonlyArray.fromIterable(this.minutes),
|
|
63
|
+
hours: ReadonlyArray.fromIterable(this.hours),
|
|
64
|
+
days: ReadonlyArray.fromIterable(this.days),
|
|
65
|
+
months: ReadonlyArray.fromIterable(this.months),
|
|
66
|
+
weekdays: ReadonlyArray.fromIterable(this.weekdays)
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
[NodeInspectSymbol](this: Cron) {
|
|
70
|
+
return this.toJSON()
|
|
71
|
+
},
|
|
72
|
+
pipe() {
|
|
73
|
+
return pipeArguments(this, arguments)
|
|
74
|
+
}
|
|
75
|
+
} as const
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Checks if a given value is a `Cron` instance.
|
|
79
|
+
*
|
|
80
|
+
* @param u - The value to check.
|
|
81
|
+
*
|
|
82
|
+
* @since 2.0.0
|
|
83
|
+
* @category guards
|
|
84
|
+
*/
|
|
85
|
+
export const isCron = (u: unknown): u is Cron => hasProperty(u, TypeId)
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Creates a `Cron` instance from.
|
|
89
|
+
*
|
|
90
|
+
* @param constraints - The cron constraints.
|
|
91
|
+
*
|
|
92
|
+
* @since 2.0.0
|
|
93
|
+
* @category constructors
|
|
94
|
+
*/
|
|
95
|
+
export const make = ({
|
|
96
|
+
days,
|
|
97
|
+
hours,
|
|
98
|
+
minutes,
|
|
99
|
+
months,
|
|
100
|
+
weekdays
|
|
101
|
+
}: {
|
|
102
|
+
readonly minutes: Iterable<number>
|
|
103
|
+
readonly hours: Iterable<number>
|
|
104
|
+
readonly days: Iterable<number>
|
|
105
|
+
readonly months: Iterable<number>
|
|
106
|
+
readonly weekdays: Iterable<number>
|
|
107
|
+
}): Cron => {
|
|
108
|
+
const o: Mutable<Cron> = Object.create(CronProto)
|
|
109
|
+
o.minutes = new Set(ReadonlyArray.sort(minutes, N.Order))
|
|
110
|
+
o.hours = new Set(ReadonlyArray.sort(hours, N.Order))
|
|
111
|
+
o.days = new Set(ReadonlyArray.sort(days, N.Order))
|
|
112
|
+
o.months = new Set(ReadonlyArray.sort(months, N.Order))
|
|
113
|
+
o.weekdays = new Set(ReadonlyArray.sort(weekdays, N.Order))
|
|
114
|
+
return o
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* @since 2.0.0
|
|
119
|
+
* @category symbol
|
|
120
|
+
*/
|
|
121
|
+
export const ParseErrorTypeId: unique symbol = Symbol.for("effect/Cron/errors/ParseError")
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* @since 2.0.0
|
|
125
|
+
* @category symbols
|
|
126
|
+
*/
|
|
127
|
+
export type ParseErrorTypeId = typeof ParseErrorTypeId
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Represents a checked exception which occurs when decoding fails.
|
|
131
|
+
*
|
|
132
|
+
* @since 2.0.0
|
|
133
|
+
* @category models
|
|
134
|
+
*/
|
|
135
|
+
export interface ParseError {
|
|
136
|
+
readonly _tag: "ParseError"
|
|
137
|
+
readonly [ParseErrorTypeId]: ParseErrorTypeId
|
|
138
|
+
readonly message: string
|
|
139
|
+
readonly input?: string
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const ParseErrorProto: Omit<ParseError, "input" | "message"> = {
|
|
143
|
+
_tag: "ParseError",
|
|
144
|
+
[ParseErrorTypeId]: ParseErrorTypeId
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const ParseError = (message: string, input?: string): ParseError => {
|
|
148
|
+
const o: Mutable<ParseError> = Object.create(ParseErrorProto)
|
|
149
|
+
o.message = message
|
|
150
|
+
if (input !== undefined) {
|
|
151
|
+
o.input = input
|
|
152
|
+
}
|
|
153
|
+
return o
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Returns `true` if the specified value is an `ParseError`, `false` otherwise.
|
|
158
|
+
*
|
|
159
|
+
* @param u - The value to check.
|
|
160
|
+
*
|
|
161
|
+
* @since 2.0.0
|
|
162
|
+
* @category guards
|
|
163
|
+
*/
|
|
164
|
+
export const isParseError = (u: unknown): u is ParseError => hasProperty(u, ParseErrorTypeId)
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Parses a cron expression into a `Cron` instance.
|
|
168
|
+
*
|
|
169
|
+
* @param cron - The cron expression to parse.
|
|
170
|
+
*
|
|
171
|
+
* @example
|
|
172
|
+
* import * as Cron from "effect/Cron"
|
|
173
|
+
* import * as Either from "effect/Either"
|
|
174
|
+
*
|
|
175
|
+
* // At 04:00 on every day-of-month from 8 through 14.
|
|
176
|
+
* assert.deepStrictEqual(Cron.parse("0 4 8-14 * *"), Either.right(Cron.make({
|
|
177
|
+
* minutes: [0],
|
|
178
|
+
* hours: [4],
|
|
179
|
+
* days: [8, 9, 10, 11, 12, 13, 14],
|
|
180
|
+
* months: [],
|
|
181
|
+
* weekdays: []
|
|
182
|
+
* })))
|
|
183
|
+
*
|
|
184
|
+
* @since 2.0.0
|
|
185
|
+
* @category constructors
|
|
186
|
+
*/
|
|
187
|
+
export const parse = (cron: string): Either.Either<ParseError, Cron> => {
|
|
188
|
+
const segments = cron.split(" ").filter(String.isNonEmpty)
|
|
189
|
+
if (segments.length !== 5) {
|
|
190
|
+
return Either.left(ParseError(`Invalid number of segments in cron expression`, cron))
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const [minutes, hours, days, months, weekdays] = segments
|
|
194
|
+
return Either.all({
|
|
195
|
+
minutes: parseSegment(minutes, minuteOptions),
|
|
196
|
+
hours: parseSegment(hours, hourOptions),
|
|
197
|
+
days: parseSegment(days, dayOptions),
|
|
198
|
+
months: parseSegment(months, monthOptions),
|
|
199
|
+
weekdays: parseSegment(weekdays, weekdayOptions)
|
|
200
|
+
}).pipe(Either.map((segments) => make(segments)))
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Checks if a given `Date` falls within an active `Cron` time window.
|
|
205
|
+
*
|
|
206
|
+
* @param cron - The `Cron` instance.
|
|
207
|
+
* @param date - The `Date` to check against.
|
|
208
|
+
*
|
|
209
|
+
* @example
|
|
210
|
+
* import * as Cron from "effect/Cron"
|
|
211
|
+
* import * as Either from "effect/Either"
|
|
212
|
+
*
|
|
213
|
+
* const cron = Either.getOrThrow(Cron.parse("0 4 8-14 * *"))
|
|
214
|
+
* assert.deepStrictEqual(Cron.match(cron, new Date("2021-01-08 04:00:00")), true)
|
|
215
|
+
* assert.deepStrictEqual(Cron.match(cron, new Date("2021-01-08 05:00:00")), false)
|
|
216
|
+
*
|
|
217
|
+
* @since 2.0.0
|
|
218
|
+
*/
|
|
219
|
+
export const match = (cron: Cron, date: Date): boolean => {
|
|
220
|
+
const { days, hours, minutes, months, weekdays } = cron
|
|
221
|
+
|
|
222
|
+
const minute = date.getMinutes()
|
|
223
|
+
if (minutes.size !== 0 && !minutes.has(minute)) {
|
|
224
|
+
return false
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const hour = date.getHours()
|
|
228
|
+
if (hours.size !== 0 && !hours.has(hour)) {
|
|
229
|
+
return false
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const month = date.getMonth() + 1
|
|
233
|
+
if (months.size !== 0 && !months.has(month)) {
|
|
234
|
+
return false
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (days.size === 0 && weekdays.size === 0) {
|
|
238
|
+
return true
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const day = date.getDate()
|
|
242
|
+
if (weekdays.size === 0) {
|
|
243
|
+
return days.has(day)
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const weekday = date.getDay()
|
|
247
|
+
if (days.size === 0) {
|
|
248
|
+
return weekdays.has(weekday)
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return days.has(day) || weekdays.has(weekday)
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Returns the next run `Date` for the given `Cron` instance.
|
|
256
|
+
*
|
|
257
|
+
* Uses the current time as a starting point if no value is provided for `now`.
|
|
258
|
+
*
|
|
259
|
+
* @example
|
|
260
|
+
* import * as Cron from "effect/Cron"
|
|
261
|
+
* import * as Either from "effect/Either"
|
|
262
|
+
*
|
|
263
|
+
* const after = new Date("2021-01-01 00:00:00")
|
|
264
|
+
* const cron = Either.getOrThrow(Cron.parse("0 4 8-14 * *"))
|
|
265
|
+
* assert.deepStrictEqual(Cron.next(cron, after), new Date("2021-01-08 04:00:00"))
|
|
266
|
+
*
|
|
267
|
+
* @param cron - The `Cron` instance.
|
|
268
|
+
* @param now - The `Date` to start searching from.
|
|
269
|
+
*
|
|
270
|
+
* @since 2.0.0
|
|
271
|
+
*/
|
|
272
|
+
export const next = (cron: Cron, now?: Date): Date => {
|
|
273
|
+
const { days, hours, minutes, months, weekdays } = cron
|
|
274
|
+
|
|
275
|
+
const restrictMinutes = minutes.size !== 0
|
|
276
|
+
const restrictHours = hours.size !== 0
|
|
277
|
+
const restrictDays = days.size !== 0
|
|
278
|
+
const restrictMonths = months.size !== 0
|
|
279
|
+
const restrictWeekdays = weekdays.size !== 0
|
|
280
|
+
|
|
281
|
+
const current = now ? new Date(now.getTime()) : new Date()
|
|
282
|
+
// Increment by one minute to ensure we don't match the current date.
|
|
283
|
+
current.setMinutes(current.getMinutes() + 1)
|
|
284
|
+
current.setSeconds(0)
|
|
285
|
+
current.setMilliseconds(0)
|
|
286
|
+
|
|
287
|
+
// Only search 8 years into the future.
|
|
288
|
+
const limit = new Date(current).setFullYear(current.getFullYear() + 8)
|
|
289
|
+
while (current.getTime() <= limit) {
|
|
290
|
+
if (restrictMonths && !months.has(current.getMonth() + 1)) {
|
|
291
|
+
current.setMonth(current.getMonth() + 1)
|
|
292
|
+
current.setDate(1)
|
|
293
|
+
current.setHours(0)
|
|
294
|
+
current.setMinutes(0)
|
|
295
|
+
continue
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (restrictDays && restrictWeekdays) {
|
|
299
|
+
if (!days.has(current.getDate()) && !weekdays.has(current.getDay())) {
|
|
300
|
+
current.setDate(current.getDate() + 1)
|
|
301
|
+
current.setHours(0)
|
|
302
|
+
current.setMinutes(0)
|
|
303
|
+
continue
|
|
304
|
+
}
|
|
305
|
+
} else if (restrictDays) {
|
|
306
|
+
if (!days.has(current.getDate())) {
|
|
307
|
+
current.setDate(current.getDate() + 1)
|
|
308
|
+
current.setHours(0)
|
|
309
|
+
current.setMinutes(0)
|
|
310
|
+
continue
|
|
311
|
+
}
|
|
312
|
+
} else if (restrictWeekdays) {
|
|
313
|
+
if (!weekdays.has(current.getDay())) {
|
|
314
|
+
current.setDate(current.getDate() + 1)
|
|
315
|
+
current.setHours(0)
|
|
316
|
+
current.setMinutes(0)
|
|
317
|
+
continue
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
if (restrictHours && !hours.has(current.getHours())) {
|
|
322
|
+
current.setHours(current.getHours() + 1)
|
|
323
|
+
current.setMinutes(0)
|
|
324
|
+
continue
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (restrictMinutes && !minutes.has(current.getMinutes())) {
|
|
328
|
+
current.setMinutes(current.getMinutes() + 1)
|
|
329
|
+
continue
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return current
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
throw new Error("Unable to find next cron date")
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Returns an `IterableIterator` which yields the sequence of `Date`s that match the `Cron` instance.
|
|
340
|
+
*
|
|
341
|
+
* @param cron - The `Cron` instance.
|
|
342
|
+
* @param now - The `Date` to start searching from.
|
|
343
|
+
*
|
|
344
|
+
* @since 2.0.0
|
|
345
|
+
*/
|
|
346
|
+
export const sequence = function*(cron: Cron, now?: Date): IterableIterator<Date> {
|
|
347
|
+
while (true) {
|
|
348
|
+
yield now = next(cron, now)
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* @category instances
|
|
354
|
+
* @since 2.0.0
|
|
355
|
+
*/
|
|
356
|
+
export const Equivalence: equivalence.Equivalence<Cron> = equivalence.make((self, that) =>
|
|
357
|
+
restrictionsEquals(self.minutes, that.minutes) &&
|
|
358
|
+
restrictionsEquals(self.hours, that.hours) &&
|
|
359
|
+
restrictionsEquals(self.days, that.days) &&
|
|
360
|
+
restrictionsEquals(self.months, that.months) &&
|
|
361
|
+
restrictionsEquals(self.weekdays, that.weekdays)
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
const restrictionsArrayEquals = equivalence.array(equivalence.number)
|
|
365
|
+
const restrictionsEquals = (self: ReadonlySet<number>, that: ReadonlySet<number>): boolean =>
|
|
366
|
+
restrictionsArrayEquals(ReadonlyArray.fromIterable(self), ReadonlyArray.fromIterable(that))
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Checks if two `Cron`s are equal.
|
|
370
|
+
*
|
|
371
|
+
* @since 2.0.0
|
|
372
|
+
* @category predicates
|
|
373
|
+
*/
|
|
374
|
+
export const equals: {
|
|
375
|
+
(that: Cron): (self: Cron) => boolean
|
|
376
|
+
(self: Cron, that: Cron): boolean
|
|
377
|
+
} = dual(2, (self: Cron, that: Cron): boolean => Equivalence(self, that))
|
|
378
|
+
|
|
379
|
+
interface SegmentOptions {
|
|
380
|
+
segment: string
|
|
381
|
+
min: number
|
|
382
|
+
max: number
|
|
383
|
+
aliases?: Record<string, number> | undefined
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
const minuteOptions: SegmentOptions = {
|
|
387
|
+
segment: "minute",
|
|
388
|
+
min: 0,
|
|
389
|
+
max: 59
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const hourOptions: SegmentOptions = {
|
|
393
|
+
segment: "hour",
|
|
394
|
+
min: 0,
|
|
395
|
+
max: 23
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
const dayOptions: SegmentOptions = {
|
|
399
|
+
segment: "day",
|
|
400
|
+
min: 1,
|
|
401
|
+
max: 31
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
const monthOptions: SegmentOptions = {
|
|
405
|
+
segment: "month",
|
|
406
|
+
min: 1,
|
|
407
|
+
max: 12,
|
|
408
|
+
aliases: {
|
|
409
|
+
jan: 1,
|
|
410
|
+
feb: 2,
|
|
411
|
+
mar: 3,
|
|
412
|
+
apr: 4,
|
|
413
|
+
may: 5,
|
|
414
|
+
jun: 6,
|
|
415
|
+
jul: 7,
|
|
416
|
+
aug: 8,
|
|
417
|
+
sep: 9,
|
|
418
|
+
oct: 10,
|
|
419
|
+
nov: 11,
|
|
420
|
+
dec: 12
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
const weekdayOptions: SegmentOptions = {
|
|
425
|
+
segment: "weekday",
|
|
426
|
+
min: 0,
|
|
427
|
+
max: 6,
|
|
428
|
+
aliases: {
|
|
429
|
+
sun: 0,
|
|
430
|
+
mon: 1,
|
|
431
|
+
tue: 2,
|
|
432
|
+
wed: 3,
|
|
433
|
+
thu: 4,
|
|
434
|
+
fri: 5,
|
|
435
|
+
sat: 6
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
const parseSegment = (
|
|
440
|
+
input: string,
|
|
441
|
+
options: SegmentOptions
|
|
442
|
+
): Either.Either<ParseError, ReadonlySet<number>> => {
|
|
443
|
+
const capacity = options.max - options.min + 1
|
|
444
|
+
const values = new Set<number>()
|
|
445
|
+
const fields = input.split(",")
|
|
446
|
+
|
|
447
|
+
for (const field of fields) {
|
|
448
|
+
const [raw, step] = splitStep(field)
|
|
449
|
+
if (raw === "*" && step === undefined) {
|
|
450
|
+
return Either.right(new Set())
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
if (step !== undefined) {
|
|
454
|
+
if (!Number.isInteger(step)) {
|
|
455
|
+
return Either.left(ParseError(`Expected step value to be a positive integer`, input))
|
|
456
|
+
}
|
|
457
|
+
if (step < 1) {
|
|
458
|
+
return Either.left(ParseError(`Expected step value to be greater than 0`, input))
|
|
459
|
+
}
|
|
460
|
+
if (step > options.max) {
|
|
461
|
+
return Either.left(ParseError(`Expected step value to be less than ${options.max}`, input))
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
if (raw === "*") {
|
|
466
|
+
for (let i = options.min; i <= options.max; i += step ?? 1) {
|
|
467
|
+
values.add(i)
|
|
468
|
+
}
|
|
469
|
+
} else {
|
|
470
|
+
const [left, right] = splitRange(raw, options.aliases)
|
|
471
|
+
if (!Number.isInteger(left)) {
|
|
472
|
+
return Either.left(ParseError(`Expected a positive integer`, input))
|
|
473
|
+
}
|
|
474
|
+
if (left < options.min || left > options.max) {
|
|
475
|
+
return Either.left(ParseError(`Expected a value between ${options.min} and ${options.max}`, input))
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
if (right === undefined) {
|
|
479
|
+
values.add(left)
|
|
480
|
+
} else {
|
|
481
|
+
if (!Number.isInteger(right)) {
|
|
482
|
+
return Either.left(ParseError(`Expected a positive integer`, input))
|
|
483
|
+
}
|
|
484
|
+
if (right < options.min || right > options.max) {
|
|
485
|
+
return Either.left(ParseError(`Expected a value between ${options.min} and ${options.max}`, input))
|
|
486
|
+
}
|
|
487
|
+
if (left > right) {
|
|
488
|
+
return Either.left(ParseError(`Invalid value range`, input))
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
for (let i = left; i <= right; i += step ?? 1) {
|
|
492
|
+
values.add(i)
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
if (values.size >= capacity) {
|
|
498
|
+
return Either.right(new Set())
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
return Either.right(values)
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
const splitStep = (input: string): [string, number | undefined] => {
|
|
506
|
+
const seperator = input.indexOf("/")
|
|
507
|
+
if (seperator !== -1) {
|
|
508
|
+
return [input.slice(0, seperator), Number(input.slice(seperator + 1))]
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
return [input, undefined]
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
const splitRange = (input: string, aliases?: Record<string, number>): [number, number | undefined] => {
|
|
515
|
+
const seperator = input.indexOf("-")
|
|
516
|
+
if (seperator !== -1) {
|
|
517
|
+
return [aliasOrValue(input.slice(0, seperator), aliases), aliasOrValue(input.slice(seperator + 1), aliases)]
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
return [aliasOrValue(input, aliases), undefined]
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
function aliasOrValue(field: string, aliases?: Record<string, number>): number {
|
|
524
|
+
return aliases?.[field.toLocaleLowerCase()] ?? Number(field)
|
|
525
|
+
}
|
package/src/Fiber.ts
CHANGED
|
@@ -148,6 +148,13 @@ export interface RuntimeFiber<out E, out A> extends Fiber<E, A>, Fiber.RuntimeVa
|
|
|
148
148
|
* already done.
|
|
149
149
|
*/
|
|
150
150
|
unsafePoll(): Exit.Exit<E, A> | null
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* In the background, interrupts the fiber as if interrupted from the
|
|
154
|
+
* specified fiber. If the fiber has already exited, the returned effect will
|
|
155
|
+
* resume immediately. Otherwise, the effect will resume when the fiber exits.
|
|
156
|
+
*/
|
|
157
|
+
unsafeInterruptAsFork(fiberId: FiberId.FiberId): void
|
|
151
158
|
}
|
|
152
159
|
|
|
153
160
|
/**
|