@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.
Files changed (69) hide show
  1. package/README.md +125 -4
  2. package/dist/build.d.ts +54 -0
  3. package/dist/build.d.ts.map +1 -0
  4. package/dist/build.js +77 -0
  5. package/dist/build.js.map +1 -0
  6. package/dist/cascade.d.ts +86 -0
  7. package/dist/cascade.d.ts.map +1 -0
  8. package/dist/cascade.js +40 -0
  9. package/dist/cascade.js.map +1 -0
  10. package/dist/context.d.ts +44 -0
  11. package/dist/context.d.ts.map +1 -0
  12. package/dist/context.js +9 -0
  13. package/dist/context.js.map +1 -0
  14. package/dist/day-rules.d.ts +23 -0
  15. package/dist/day-rules.d.ts.map +1 -0
  16. package/dist/day-rules.js +102 -0
  17. package/dist/day-rules.js.map +1 -0
  18. package/dist/index.d.ts +26 -2
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +17 -2
  21. package/dist/index.js.map +1 -1
  22. package/dist/interpret.d.ts +31 -0
  23. package/dist/interpret.d.ts.map +1 -0
  24. package/dist/interpret.js +99 -0
  25. package/dist/interpret.js.map +1 -0
  26. package/dist/interval-stream.d.ts +11 -4
  27. package/dist/interval-stream.d.ts.map +1 -1
  28. package/dist/interval-stream.js.map +1 -1
  29. package/dist/parse-fields.d.ts +30 -0
  30. package/dist/parse-fields.d.ts.map +1 -0
  31. package/dist/parse-fields.js +93 -0
  32. package/dist/parse-fields.js.map +1 -0
  33. package/dist/parse.d.ts +22 -0
  34. package/dist/parse.d.ts.map +1 -0
  35. package/dist/parse.js +115 -0
  36. package/dist/parse.js.map +1 -0
  37. package/dist/plain-forms.d.ts +22 -0
  38. package/dist/plain-forms.d.ts.map +1 -0
  39. package/dist/plain-forms.js +54 -0
  40. package/dist/plain-forms.js.map +1 -0
  41. package/dist/query.d.ts +67 -0
  42. package/dist/query.d.ts.map +1 -0
  43. package/dist/query.js +121 -0
  44. package/dist/query.js.map +1 -0
  45. package/dist/resolve.d.ts +25 -0
  46. package/dist/resolve.d.ts.map +1 -0
  47. package/dist/resolve.js +80 -0
  48. package/dist/resolve.js.map +1 -0
  49. package/dist/rota.d.ts +49 -0
  50. package/dist/rota.d.ts.map +1 -0
  51. package/dist/rota.js +47 -0
  52. package/dist/rota.js.map +1 -0
  53. package/dist/rule.d.ts +73 -0
  54. package/dist/rule.d.ts.map +1 -0
  55. package/dist/rule.js +20 -0
  56. package/dist/rule.js.map +1 -0
  57. package/dist/schedule.d.ts +69 -0
  58. package/dist/schedule.d.ts.map +1 -0
  59. package/dist/schedule.js +85 -0
  60. package/dist/schedule.js.map +1 -0
  61. package/dist/time-rules.d.ts +18 -0
  62. package/dist/time-rules.d.ts.map +1 -0
  63. package/dist/time-rules.js +55 -0
  64. package/dist/time-rules.js.map +1 -0
  65. package/dist/valued-stream.d.ts +42 -0
  66. package/dist/valued-stream.d.ts.map +1 -0
  67. package/dist/valued-stream.js +77 -0
  68. package/dist/valued-stream.js.map +1 -0
  69. package/package.json +1 -1
@@ -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.1.0-beta.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",