@kensio/quando 0.1.0-beta.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +125 -4
- package/dist/build.d.ts +54 -0
- package/dist/build.d.ts.map +1 -0
- package/dist/build.js +77 -0
- package/dist/build.js.map +1 -0
- package/dist/cascade.d.ts +86 -0
- package/dist/cascade.d.ts.map +1 -0
- package/dist/cascade.js +40 -0
- package/dist/cascade.js.map +1 -0
- package/dist/context.d.ts +44 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +9 -0
- package/dist/context.js.map +1 -0
- package/dist/day-rules.d.ts +23 -0
- package/dist/day-rules.d.ts.map +1 -0
- package/dist/day-rules.js +102 -0
- package/dist/day-rules.js.map +1 -0
- package/dist/index.d.ts +26 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -2
- package/dist/index.js.map +1 -1
- package/dist/interpret.d.ts +31 -0
- package/dist/interpret.d.ts.map +1 -0
- package/dist/interpret.js +99 -0
- package/dist/interpret.js.map +1 -0
- package/dist/interval-stream.d.ts +11 -4
- package/dist/interval-stream.d.ts.map +1 -1
- package/dist/interval-stream.js.map +1 -1
- package/dist/parse-fields.d.ts +30 -0
- package/dist/parse-fields.d.ts.map +1 -0
- package/dist/parse-fields.js +93 -0
- package/dist/parse-fields.js.map +1 -0
- package/dist/parse.d.ts +22 -0
- package/dist/parse.d.ts.map +1 -0
- package/dist/parse.js +115 -0
- package/dist/parse.js.map +1 -0
- package/dist/plain-forms.d.ts +22 -0
- package/dist/plain-forms.d.ts.map +1 -0
- package/dist/plain-forms.js +54 -0
- package/dist/plain-forms.js.map +1 -0
- package/dist/query.d.ts +67 -0
- package/dist/query.d.ts.map +1 -0
- package/dist/query.js +121 -0
- package/dist/query.js.map +1 -0
- package/dist/resolve.d.ts +25 -0
- package/dist/resolve.d.ts.map +1 -0
- package/dist/resolve.js +80 -0
- package/dist/resolve.js.map +1 -0
- package/dist/rota.d.ts +49 -0
- package/dist/rota.d.ts.map +1 -0
- package/dist/rota.js +47 -0
- package/dist/rota.js.map +1 -0
- package/dist/rule.d.ts +73 -0
- package/dist/rule.d.ts.map +1 -0
- package/dist/rule.js +20 -0
- package/dist/rule.js.map +1 -0
- package/dist/schedule.d.ts +69 -0
- package/dist/schedule.d.ts.map +1 -0
- package/dist/schedule.js +85 -0
- package/dist/schedule.js.map +1 -0
- package/dist/time-rules.d.ts +18 -0
- package/dist/time-rules.d.ts.map +1 -0
- package/dist/time-rules.js +55 -0
- package/dist/time-rules.js.map +1 -0
- package/dist/valued-stream.d.ts +42 -0
- package/dist/valued-stream.d.ts.map +1 -0
- package/dist/valued-stream.js +77 -0
- package/dist/valued-stream.js.map +1 -0
- package/package.json +1 -1
package/dist/schedule.js
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Opening hours, in the words people use for them.
|
|
3
|
+
*
|
|
4
|
+
* Everything here is a cascade underneath, and a `Schedule` *is* one — the
|
|
5
|
+
* same trick the rule builders use, so it serialises to the same document and
|
|
6
|
+
* `resolve` reads it unchanged. What this adds is vocabulary: nobody running a
|
|
7
|
+
* warehouse says "add a layer to the cascade", they say we are open weekdays,
|
|
8
|
+
* closed on bank holidays, and on the eleventh we close at three.
|
|
9
|
+
*
|
|
10
|
+
* Those three read in the order they are said, and each one outranks what came
|
|
11
|
+
* before it — which is the same precedence a cascade has, arrived at by
|
|
12
|
+
* writing the sentence in the obvious order rather than by knowing the rule.
|
|
13
|
+
*/
|
|
14
|
+
import { all } from "./build.js";
|
|
15
|
+
import { layer, replace } from "./cascade.js";
|
|
16
|
+
import { duration } from "./interval.js";
|
|
17
|
+
import { asDays, asHours } from "./plain-forms.js";
|
|
18
|
+
import { resolve } from "./resolve.js";
|
|
19
|
+
import { take } from "./stream.js";
|
|
20
|
+
/** Zero, as a duration to accumulate onto. */
|
|
21
|
+
const NOTHING = Temporal.Duration.from({ seconds: 0 });
|
|
22
|
+
function open(scope, hours) {
|
|
23
|
+
const when = asDays(scope);
|
|
24
|
+
return layer(hours === undefined ? when : all(when, asHours(hours)), true);
|
|
25
|
+
}
|
|
26
|
+
function build(layers) {
|
|
27
|
+
const self = {
|
|
28
|
+
type: "cascade",
|
|
29
|
+
layers,
|
|
30
|
+
open: (scope, hours) => build([...layers, open(scope, hours)]),
|
|
31
|
+
closed: (scope) => build([...layers, layer(asDays(scope), false)]),
|
|
32
|
+
hoursOn: (day, hours) => build([...layers, replace(asDays(day), asHours(hours))]),
|
|
33
|
+
isOpen: (at) => {
|
|
34
|
+
// The smallest window there is, so this terminates whatever the layers
|
|
35
|
+
// say — the same trick `activeAt` uses on a rule.
|
|
36
|
+
const moment = { from: at, to: at.add({ nanoseconds: 1 }) };
|
|
37
|
+
const [now] = take(resolve(self, moment), 1);
|
|
38
|
+
return now?.value ?? false;
|
|
39
|
+
},
|
|
40
|
+
opensNext: (at, within) => {
|
|
41
|
+
const search = within === undefined ? { from: at } : { from: at, to: at.add(within) };
|
|
42
|
+
for (const period of resolve(self, search)) {
|
|
43
|
+
if (!period.value) {
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (within === undefined || period.start === undefined) {
|
|
47
|
+
return period;
|
|
48
|
+
}
|
|
49
|
+
// The horizon bounds how far to look, not what is found. Everything
|
|
50
|
+
// resolved against a context is clipped to it, so a stretch that runs
|
|
51
|
+
// past the horizon comes back ending at the horizon — which reads as a
|
|
52
|
+
// closing time and is not one. Read it again from its own start, with
|
|
53
|
+
// nothing to clip it, so the end is when it really closes.
|
|
54
|
+
const [whole] = take(resolve(self, { from: period.start }), 1);
|
|
55
|
+
return whole ?? period;
|
|
56
|
+
}
|
|
57
|
+
return;
|
|
58
|
+
},
|
|
59
|
+
openBetween: (from, to) => {
|
|
60
|
+
let total = NOTHING;
|
|
61
|
+
for (const period of resolve(self, { from, to })) {
|
|
62
|
+
const length = period.value ? duration(period) : undefined;
|
|
63
|
+
if (length !== undefined) {
|
|
64
|
+
total = total.add(length);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return total.round({ largestUnit: "hour" });
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
return self;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* An empty schedule: open for nothing until something says otherwise.
|
|
74
|
+
*
|
|
75
|
+
* ```ts
|
|
76
|
+
* const openingHours = schedule()
|
|
77
|
+
* .open(weekdays(), "09:00-17:00")
|
|
78
|
+
* .closed("2026-12-25")
|
|
79
|
+
* .hoursOn("2026-03-11", "09:00-15:00");
|
|
80
|
+
* ```
|
|
81
|
+
*/
|
|
82
|
+
export function schedule() {
|
|
83
|
+
return build([]);
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=schedule.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schedule.js","sourceRoot":"","sources":["../src/schedule.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The rule that selects a wall-clock window within each day.
|
|
3
|
+
*
|
|
4
|
+
* Wall clock rather than elapsed time is the whole point: across a daylight
|
|
5
|
+
* saving transition the clock times stay put and the real length of the window
|
|
6
|
+
* changes, which is what a schedule means by "nine to five".
|
|
7
|
+
*/
|
|
8
|
+
import { type Context } from "./context.js";
|
|
9
|
+
import type { IntervalStream } from "./interval-stream.js";
|
|
10
|
+
/**
|
|
11
|
+
* A wall-clock window within each day, endless unless the context bounds it.
|
|
12
|
+
*
|
|
13
|
+
* Starts a day earlier than the context does, because a window that wraps past
|
|
14
|
+
* midnight may have opened yesterday and still be running. Clipping to the
|
|
15
|
+
* window drops whatever that turns up too early.
|
|
16
|
+
*/
|
|
17
|
+
export declare function timeOfDayIntervals(context: Context, from: string, to: string, zone?: string): IntervalStream;
|
|
18
|
+
//# sourceMappingURL=time-rules.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"time-rules.d.ts","sourceRoot":"","sources":["../src/time-rules.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,KAAK,OAAO,EAAU,MAAM,cAAc,CAAC;AACpD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE3D;;;;;;GAMG;AACH,wBAAiB,kBAAkB,CACjC,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,MAAM,EACV,IAAI,CAAC,EAAE,MAAM,GACZ,cAAc,CAgDhB"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The rule that selects a wall-clock window within each day.
|
|
3
|
+
*
|
|
4
|
+
* Wall clock rather than elapsed time is the whole point: across a daylight
|
|
5
|
+
* saving transition the clock times stay put and the real length of the window
|
|
6
|
+
* changes, which is what a schedule means by "nine to five".
|
|
7
|
+
*/
|
|
8
|
+
import { zoneOf } from "./context.js";
|
|
9
|
+
/**
|
|
10
|
+
* A wall-clock window within each day, endless unless the context bounds it.
|
|
11
|
+
*
|
|
12
|
+
* Starts a day earlier than the context does, because a window that wraps past
|
|
13
|
+
* midnight may have opened yesterday and still be running. Clipping to the
|
|
14
|
+
* window drops whatever that turns up too early.
|
|
15
|
+
*/
|
|
16
|
+
export function* timeOfDayIntervals(context, from, to, zone) {
|
|
17
|
+
const inZone = zoneOf(context, zone);
|
|
18
|
+
const opens = Temporal.PlainTime.from(from);
|
|
19
|
+
const closes = Temporal.PlainTime.from(to);
|
|
20
|
+
if (Temporal.PlainTime.compare(opens, closes) === 0) {
|
|
21
|
+
throw new RangeError(`A time-of-day window from ${from} to ${to} has the same start and end. ` +
|
|
22
|
+
`Use { type: "always" } for a whole day.`);
|
|
23
|
+
}
|
|
24
|
+
// Earlier `to` than `from` means the window runs past midnight into the day
|
|
25
|
+
// after — a night shift, not an empty window.
|
|
26
|
+
const wraps = Temporal.PlainTime.compare(closes, opens) < 0;
|
|
27
|
+
const stop = context.to;
|
|
28
|
+
let date = context.from
|
|
29
|
+
.withTimeZone(inZone)
|
|
30
|
+
.toPlainDate()
|
|
31
|
+
.subtract({ days: 1 });
|
|
32
|
+
for (;;) {
|
|
33
|
+
const start = date.toZonedDateTime({ timeZone: inZone, plainTime: opens });
|
|
34
|
+
if (stop !== undefined &&
|
|
35
|
+
Temporal.ZonedDateTime.compare(start, stop) >= 0) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const closing = wraps ? date.add({ days: 1 }) : date;
|
|
39
|
+
const end = closing.toZonedDateTime({
|
|
40
|
+
timeZone: inZone,
|
|
41
|
+
plainTime: closes,
|
|
42
|
+
});
|
|
43
|
+
// A window can collapse to nothing on the morning clocks go forward. Both
|
|
44
|
+
// ends of 01:00-02:00 in London on 2026-03-29 resolve to the same instant,
|
|
45
|
+
// because the hour between them does not exist and Temporal's default
|
|
46
|
+
// disambiguation moves a nonexistent time forward to the far side of the
|
|
47
|
+
// gap. Yielding that would put a zero-length interval into a stream whose
|
|
48
|
+
// contract says there are none.
|
|
49
|
+
if (Temporal.ZonedDateTime.compare(start, end) < 0) {
|
|
50
|
+
yield { start, end };
|
|
51
|
+
}
|
|
52
|
+
date = date.add({ days: 1 });
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=time-rules.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"time-rules.js","sourceRoot":"","sources":["../src/time-rules.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAgB,MAAM,EAAE,MAAM,cAAc,CAAC;AAGpD;;;;;;GAMG;AACH,MAAM,SAAS,CAAC,CAAC,kBAAkB,CACjC,OAAgB,EAChB,IAAY,EACZ,EAAU,EACV,IAAa;IAEb,MAAM,MAAM,GAAG,MAAM,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IACrC,MAAM,KAAK,GAAG,QAAQ,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC5C,MAAM,MAAM,GAAG,QAAQ,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAE3C,IAAI,QAAQ,CAAC,SAAS,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;QACpD,MAAM,IAAI,UAAU,CAClB,6BAA6B,IAAI,OAAO,EAAE,+BAA+B;YACvE,yCAAyC,CAC5C,CAAC;IACJ,CAAC;IAED,4EAA4E;IAC5E,8CAA8C;IAC9C,MAAM,KAAK,GAAG,QAAQ,CAAC,SAAS,CAAC,OAAO,CAAC,MAAM,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;IAC5D,MAAM,IAAI,GAAG,OAAO,CAAC,EAAE,CAAC;IACxB,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI;SACpB,YAAY,CAAC,MAAM,CAAC;SACpB,WAAW,EAAE;SACb,QAAQ,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC;IAEzB,SAAS,CAAC;QACR,MAAM,KAAK,GAAG,IAAI,CAAC,eAAe,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC;QAC3E,IACE,IAAI,KAAK,SAAS;YAClB,QAAQ,CAAC,aAAa,CAAC,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,EAChD,CAAC;YACD,OAAO;QACT,CAAC;QAED,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QACrD,MAAM,GAAG,GAAG,OAAO,CAAC,eAAe,CAAC;YAClC,QAAQ,EAAE,MAAM;YAChB,SAAS,EAAE,MAAM;SAClB,CAAC,CAAC;QAEH,0EAA0E;QAC1E,2EAA2E;QAC3E,sEAAsE;QACtE,yEAAyE;QACzE,0EAA0E;QAC1E,gCAAgC;QAChC,IAAI,QAAQ,CAAC,aAAa,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;YACnD,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC;QACvB,CAAC;QAED,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC;IAC/B,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Streams of intervals that carry values, and the two operations resolution
|
|
3
|
+
* needs over them.
|
|
4
|
+
*
|
|
5
|
+
* The same shape as `interval-stream.ts` one level down, and deliberately
|
|
6
|
+
* separate from it: those sweeps are set algebra over *when*, and nothing in
|
|
7
|
+
* them has an opinion about values. These two do nothing but carry values
|
|
8
|
+
* along, which is why neither needs the overlap arithmetic next door.
|
|
9
|
+
*/
|
|
10
|
+
import type { Valued } from "./cascade.js";
|
|
11
|
+
/**
|
|
12
|
+
* A lazy sequence of valued intervals.
|
|
13
|
+
*
|
|
14
|
+
* Same contract as `IntervalStream` — ascending by start, non-overlapping,
|
|
15
|
+
* coalesced — with one addition: touching intervals carrying the same value
|
|
16
|
+
* are merged, so where two intervals do touch, the values on either side of
|
|
17
|
+
* the boundary differ.
|
|
18
|
+
*/
|
|
19
|
+
export type ValuedStream<V> = Iterable<Valued<V>>;
|
|
20
|
+
/**
|
|
21
|
+
* One ascending stream from many.
|
|
22
|
+
*
|
|
23
|
+
* The sources are disjoint by construction — a moment is won by exactly one
|
|
24
|
+
* layer — so this only has to take whichever starts earliest, with none of the
|
|
25
|
+
* overlap arithmetic the interval sweeps need.
|
|
26
|
+
*/
|
|
27
|
+
export declare function interleave<V>(sources: readonly ValuedStream<V>[]): ValuedStream<V>;
|
|
28
|
+
/**
|
|
29
|
+
* Adjacent intervals carrying the same value are one interval.
|
|
30
|
+
*
|
|
31
|
+
* Without this, a run that happens to be split across two layers comes back as
|
|
32
|
+
* two touching intervals with equal values — the same assignment said twice,
|
|
33
|
+
* and a stream that no longer satisfies the coalesced contract the rest of the
|
|
34
|
+
* library keeps.
|
|
35
|
+
*
|
|
36
|
+
* Sameness is `Object.is`, so primitives and shared references merge while
|
|
37
|
+
* structurally-equal objects do not. That is the conservative way round: it
|
|
38
|
+
* splits an interval that could have been merged rather than merging two the
|
|
39
|
+
* caller meant to keep apart.
|
|
40
|
+
*/
|
|
41
|
+
export declare function coalesce<V>(source: ValuedStream<V>): ValuedStream<V>;
|
|
42
|
+
//# sourceMappingURL=valued-stream.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"valued-stream.d.ts","sourceRoot":"","sources":["../src/valued-stream.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAI3C;;;;;;;GAOG;AACH,MAAM,MAAM,YAAY,CAAC,CAAC,IAAI,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;AAElD;;;;;;GAMG;AACH,wBAAiB,UAAU,CAAC,CAAC,EAC3B,OAAO,EAAE,SAAS,YAAY,CAAC,CAAC,CAAC,EAAE,GAClC,YAAY,CAAC,CAAC,CAAC,CAyBjB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAiB,QAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC,CAmBrE"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Streams of intervals that carry values, and the two operations resolution
|
|
3
|
+
* needs over them.
|
|
4
|
+
*
|
|
5
|
+
* The same shape as `interval-stream.ts` one level down, and deliberately
|
|
6
|
+
* separate from it: those sweeps are set algebra over *when*, and nothing in
|
|
7
|
+
* them has an opinion about values. These two do nothing but carry values
|
|
8
|
+
* along, which is why neither needs the overlap arithmetic next door.
|
|
9
|
+
*/
|
|
10
|
+
import { compareStarts } from "./interval.js";
|
|
11
|
+
import { peekable } from "./stream.js";
|
|
12
|
+
/**
|
|
13
|
+
* One ascending stream from many.
|
|
14
|
+
*
|
|
15
|
+
* The sources are disjoint by construction — a moment is won by exactly one
|
|
16
|
+
* layer — so this only has to take whichever starts earliest, with none of the
|
|
17
|
+
* overlap arithmetic the interval sweeps need.
|
|
18
|
+
*/
|
|
19
|
+
export function* interleave(sources) {
|
|
20
|
+
const fronts = sources.map((source) => peekable(source));
|
|
21
|
+
for (;;) {
|
|
22
|
+
let earliest;
|
|
23
|
+
let from;
|
|
24
|
+
for (const front of fronts) {
|
|
25
|
+
const next = front.peek();
|
|
26
|
+
if (next !== undefined &&
|
|
27
|
+
(earliest === undefined ||
|
|
28
|
+
compareStarts(next.start, earliest.start) < 0)) {
|
|
29
|
+
earliest = next;
|
|
30
|
+
from = front;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
if (from === undefined || earliest === undefined) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
from.drop();
|
|
37
|
+
yield earliest;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Adjacent intervals carrying the same value are one interval.
|
|
42
|
+
*
|
|
43
|
+
* Without this, a run that happens to be split across two layers comes back as
|
|
44
|
+
* two touching intervals with equal values — the same assignment said twice,
|
|
45
|
+
* and a stream that no longer satisfies the coalesced contract the rest of the
|
|
46
|
+
* library keeps.
|
|
47
|
+
*
|
|
48
|
+
* Sameness is `Object.is`, so primitives and shared references merge while
|
|
49
|
+
* structurally-equal objects do not. That is the conservative way round: it
|
|
50
|
+
* splits an interval that could have been merged rather than merging two the
|
|
51
|
+
* caller meant to keep apart.
|
|
52
|
+
*/
|
|
53
|
+
export function* coalesce(source) {
|
|
54
|
+
let open;
|
|
55
|
+
for (const next of source) {
|
|
56
|
+
if (open === undefined) {
|
|
57
|
+
open = next;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
if (Object.is(open.value, next.value) && touches(open, next)) {
|
|
61
|
+
open = { ...open, end: next.end };
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
yield open;
|
|
65
|
+
open = next;
|
|
66
|
+
}
|
|
67
|
+
if (open !== undefined) {
|
|
68
|
+
yield open;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/** Whether one interval ends exactly where the next begins. */
|
|
72
|
+
function touches(open, next) {
|
|
73
|
+
return (open.end !== undefined &&
|
|
74
|
+
next.start !== undefined &&
|
|
75
|
+
Temporal.ZonedDateTime.compare(open.end, next.start) === 0);
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=valued-stream.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"valued-stream.js","sourceRoot":"","sources":["../src/valued-stream.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAYvC;;;;;;GAMG;AACH,MAAM,SAAS,CAAC,CAAC,UAAU,CACzB,OAAmC;IAEnC,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;IAEzD,SAAS,CAAC;QACR,IAAI,QAA+B,CAAC;QACpC,IAAI,IAAyC,CAAC;QAE9C,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YAC3B,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;YAC1B,IACE,IAAI,KAAK,SAAS;gBAClB,CAAC,QAAQ,KAAK,SAAS;oBACrB,aAAa,CAAC,IAAI,CAAC,KAAK,EAAE,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,EAChD,CAAC;gBACD,QAAQ,GAAG,IAAI,CAAC;gBAChB,IAAI,GAAG,KAAK,CAAC;YACf,CAAC;QACH,CAAC;QAED,IAAI,IAAI,KAAK,SAAS,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YACjD,OAAO;QACT,CAAC;QACD,IAAI,CAAC,IAAI,EAAE,CAAC;QACZ,MAAM,QAAQ,CAAC;IACjB,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,SAAS,CAAC,CAAC,QAAQ,CAAI,MAAuB;IAClD,IAAI,IAA2B,CAAC;IAEhC,KAAK,MAAM,IAAI,IAAI,MAAM,EAAE,CAAC;QAC1B,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;YACvB,IAAI,GAAG,IAAI,CAAC;YACZ,SAAS;QACX,CAAC;QACD,IAAI,MAAM,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,CAAC;YAC7D,IAAI,GAAG,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC;YAClC,SAAS;QACX,CAAC;QACD,MAAM,IAAI,CAAC;QACX,IAAI,GAAG,IAAI,CAAC;IACd,CAAC;IAED,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,MAAM,IAAI,CAAC;IACb,CAAC;AACH,CAAC;AAED,+DAA+D;AAC/D,SAAS,OAAO,CAAC,IAAqB,EAAE,IAAqB;IAC3D,OAAO,CACL,IAAI,CAAC,GAAG,KAAK,SAAS;QACtB,IAAI,CAAC,KAAK,KAAK,SAAS;QACxB,QAAQ,CAAC,aAAa,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAC3D,CAAC;AACJ,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kensio/quando",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Declarative temporal rules for schedules, deadlines, constraints, and exceptions",
|
|
5
5
|
"repository": "https://github.com/KensioSoftware/quando",
|
|
6
6
|
"homepage": "https://quandojs.dev",
|