@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
package/README.md CHANGED
@@ -2,8 +2,129 @@
2
2
 
3
3
  Declarative temporal rules for schedules, deadlines, constraints, and exceptions.
4
4
 
5
- ## Status
5
+ Quando answers one question: **given these rules about time, when does something
6
+ happen?** `Temporal` tells you what a moment _is_; Quando tells you when
7
+ something _occurs_ — "weekdays nine to five, except bank holidays", and then
8
+ what that implies about now, about a window, and about three working hours from
9
+ here.
6
10
 
7
- Early. The API is still being designed and nothing is published yet, so there is
8
- nothing here to install or call. `src/index.ts` is a placeholder that exists to
9
- keep the build and test pipeline honest until it has real code to run over.
11
+ ```bash
12
+ npm install @kensio/quando
13
+ ```
14
+
15
+ Requires a runtime with `Temporal`: **Node 26 or later**, or a browser that
16
+ implements it. Quando reads the global rather than importing a polyfill, so it
17
+ has no runtime dependencies; anywhere without `Temporal` natively can load
18
+ `temporal-polyfill` first and everything here works untouched.
19
+
20
+ > The rule language, builder, parser and queries below are on `main` and go out
21
+ > in the next release. `0.1.0` on npm carries only the interval core.
22
+
23
+ ## A rule
24
+
25
+ ```ts
26
+ import { timeOfDay, weekdays } from "@kensio/quando";
27
+
28
+ const openingHours = weekdays().and(timeOfDay("09:00", "17:00"));
29
+ ```
30
+
31
+ There is no build step to that: it is already the rule, a plain JSON-shaped
32
+ object with methods hanging off it. `JSON.stringify` gives you a document you
33
+ can store, and `parseRule` gives you the rule back.
34
+
35
+ ## Four questions
36
+
37
+ ```ts
38
+ import { activeAt, advanceBy, elapsed, next } from "@kensio/quando";
39
+
40
+ const friday = Temporal.ZonedDateTime.from("2026-03-13T16:55[Europe/London]");
41
+
42
+ // Is it open?
43
+ activeAt(openingHours, friday);
44
+ // → true
45
+
46
+ // When does it next open?
47
+ next(openingHours, { from: friday.add({ hours: 2 }) })?.start?.toString();
48
+ // → 2026-03-16T09:00:00+00:00[Europe/London]
49
+
50
+ // How much opening is there this week?
51
+ elapsed(openingHours, {
52
+ from: Temporal.ZonedDateTime.from("2026-03-09T00:00[Europe/London]"),
53
+ to: Temporal.ZonedDateTime.from("2026-03-16T00:00[Europe/London]"),
54
+ }).toString();
55
+ // → PT40H
56
+
57
+ // Three hours of packing that only count while it is open — when is it done?
58
+ advanceBy(friday, Temporal.Duration.from({ hours: 3 }), {
59
+ during: openingHours,
60
+ })?.toString();
61
+ // → 2026-03-16T11:55:00+00:00[Europe/London]
62
+ ```
63
+
64
+ That last one is why the library exists, and it is the one that is genuinely
65
+ awkward to do by hand.
66
+
67
+ ## Or say it the way you would say it
68
+
69
+ For opening hours and rotas there is a plainer front door, which builds the same
70
+ thing:
71
+
72
+ ```ts
73
+ import { rota, schedule, weekdays, weekends } from "@kensio/quando";
74
+
75
+ const openingHours = schedule()
76
+ .open(weekdays(), "09:00-17:00")
77
+ .closed("2026-12-25")
78
+ .hoursOn("2026-03-11", "09:00-15:00"); // close early, just that day
79
+
80
+ openingHours.isOpen(friday);
81
+ // → true
82
+
83
+ const onCall = rota().assign(weekdays(), "alice").assign(weekends(), "bob");
84
+
85
+ onCall.whoIsOn(friday);
86
+ // → "alice"
87
+ ```
88
+
89
+ Each line outranks the ones above it, so exceptions read in the order you would
90
+ say them. See [schedules and rotas](docs/schedules/).
91
+
92
+ ## Why intervals
93
+
94
+ A rule does not answer about an instant — it produces the intervals over which
95
+ it holds. Anything that samples has to choose a step size, trading correctness
96
+ against speed, and every derived question becomes the same loop. Producing
97
+ intervals turns those loops into arithmetic: working time in a window is a sum,
98
+ three working days from now is a walk, and a rule that can never be satisfied
99
+ yields nothing instead of never finishing.
100
+
101
+ [Concepts](docs/concepts/) has the long version.
102
+
103
+ ## Documentation
104
+
105
+ [quandojs.dev](https://quandojs.dev), which is built from [`docs/`](docs/) in
106
+ this repository:
107
+
108
+ - [Getting started](docs/getting-started/) — requirements, install, first query
109
+ - [Concepts](docs/concepts/) — the model, and why it is that shape
110
+ - [Rules](docs/rules/) — every rule type, and what each produces
111
+ - [Queries](docs/queries/) — the four questions, and termination
112
+ - [Time zones](docs/time-zones/) — wall clock against elapsed time
113
+ - [Serialisation](docs/serialisation/) — the JSON form and its boundary
114
+ - [Schedules and rotas](docs/schedules/) — opening hours and who is on, plainly
115
+ - [Cascades](docs/cascades/) — layers carrying values, resolved by precedence
116
+ - [API](docs/api/) — everything the package exports
117
+
118
+ ## What it is not
119
+
120
+ Not a date library — `Temporal` is that, and Quando is built on it. Not a
121
+ scheduler: Quando calculates _when_ and never fires anything, so its answer is
122
+ your scheduler's input. Not storage, and not a holiday data provider; calendar
123
+ data belongs in satellite packages so that the core carries none.
124
+
125
+ Cascades carry values by precedence; merging values that should add rather than
126
+ displace is designed and not yet built, along with estimates and a command line.
127
+
128
+ ## Licence
129
+
130
+ [Apache-2.0](LICENSE).
@@ -0,0 +1,54 @@
1
+ /**
2
+ * A readable way to write a rule, which is also the rule.
3
+ *
4
+ * `weekdays().and(timeOfDay("09:00", "17:00"))` reads better than the nested
5
+ * object literal it stands for — and it *is* that object literal, with methods
6
+ * hanging off it. `JSON.stringify` omits function-valued properties, so a built
7
+ * rule serialises to exactly the document a hand-written one would, with no
8
+ * `.build()` step and nothing to unwrap.
9
+ *
10
+ * That is the whole trick: there is no builder type to convert out of, because
11
+ * a built rule already satisfies `Rule`.
12
+ */
13
+ import type { AllRule, AlwaysRule, AnyRule, DatesRule, DaysOfWeekRule, NeverRule, NotRule, Rule, TimeOfDayRule, Weekday } from "./rule.js";
14
+ /** A rule, plus the methods for combining it with others. */
15
+ export type Built<R extends Rule> = R & {
16
+ /** Both this and the others must hold. */
17
+ readonly and: (...others: readonly Rule[]) => Built<AllRule>;
18
+ /** This or any of the others. */
19
+ readonly or: (...others: readonly Rule[]) => Built<AnyRule>;
20
+ /**
21
+ * This, minus the times the others cover.
22
+ *
23
+ * The common shape by far — opening hours except holidays, a schedule except
24
+ * a shutdown — and worth its own method because writing it out is
25
+ * `all(this, not(any(others)))`, which reads like nothing at all.
26
+ */
27
+ readonly except: (...others: readonly Rule[]) => Built<AllRule>;
28
+ };
29
+ /** All of time. */
30
+ export declare function always(): Built<AlwaysRule>;
31
+ /** No time at all. */
32
+ export declare function never(): Built<NeverRule>;
33
+ /** Whole days, by day of the week. */
34
+ export declare function daysOfWeek(...days: readonly Weekday[]): Built<DaysOfWeekRule>;
35
+ /** Monday to Friday. */
36
+ export declare function weekdays(): Built<DaysOfWeekRule>;
37
+ /** Saturday and Sunday. */
38
+ export declare function weekends(): Built<DaysOfWeekRule>;
39
+ /**
40
+ * A wall-clock window within each day. A `to` earlier than `from` wraps past
41
+ * midnight, so `timeOfDay("22:00", "06:00")` is a night shift.
42
+ */
43
+ export declare function timeOfDay(from: string, to: string, zone?: string): Built<TimeOfDayRule>;
44
+ /** Whole days, by date. */
45
+ export declare function dates(...days: readonly string[]): Built<DatesRule>;
46
+ /** Every one of these must hold. With none, all of time. */
47
+ export declare function all(...rules: readonly Rule[]): Built<AllRule>;
48
+ /** Any one of these. With none, no time at all. */
49
+ export declare function any(...rules: readonly Rule[]): Built<AnyRule>;
50
+ /** The times a rule does not cover. */
51
+ export declare function not(rule: Rule): Built<NotRule>;
52
+ /** Whole days, by the zone a day is measured in. */
53
+ export declare function inZone<R extends DaysOfWeekRule | DatesRule | TimeOfDayRule>(rule: R, zone: string): Built<R>;
54
+ //# sourceMappingURL=build.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../src/build.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EACV,OAAO,EACP,UAAU,EACV,OAAO,EACP,SAAS,EACT,cAAc,EACd,SAAS,EACT,OAAO,EACP,IAAI,EACJ,aAAa,EACb,OAAO,EACR,MAAM,WAAW,CAAC;AAEnB,6DAA6D;AAC7D,MAAM,MAAM,KAAK,CAAC,CAAC,SAAS,IAAI,IAAI,CAAC,GAAG;IACtC,0CAA0C;IAC1C,QAAQ,CAAC,GAAG,EAAE,CAAC,GAAG,MAAM,EAAE,SAAS,IAAI,EAAE,KAAK,KAAK,CAAC,OAAO,CAAC,CAAC;IAC7D,iCAAiC;IACjC,QAAQ,CAAC,EAAE,EAAE,CAAC,GAAG,MAAM,EAAE,SAAS,IAAI,EAAE,KAAK,KAAK,CAAC,OAAO,CAAC,CAAC;IAC5D;;;;;;OAMG;IACH,QAAQ,CAAC,MAAM,EAAE,CAAC,GAAG,MAAM,EAAE,SAAS,IAAI,EAAE,KAAK,KAAK,CAAC,OAAO,CAAC,CAAC;CACjE,CAAC;AAmBF,mBAAmB;AACnB,wBAAgB,MAAM,IAAI,KAAK,CAAC,UAAU,CAAC,CAE1C;AAED,sBAAsB;AACtB,wBAAgB,KAAK,IAAI,KAAK,CAAC,SAAS,CAAC,CAExC;AAED,sCAAsC;AACtC,wBAAgB,UAAU,CAAC,GAAG,IAAI,EAAE,SAAS,OAAO,EAAE,GAAG,KAAK,CAAC,cAAc,CAAC,CAE7E;AAED,wBAAwB;AACxB,wBAAgB,QAAQ,IAAI,KAAK,CAAC,cAAc,CAAC,CAEhD;AAED,2BAA2B;AAC3B,wBAAgB,QAAQ,IAAI,KAAK,CAAC,cAAc,CAAC,CAEhD;AAED;;;GAGG;AACH,wBAAgB,SAAS,CACvB,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,MAAM,EACV,IAAI,CAAC,EAAE,MAAM,GACZ,KAAK,CAAC,aAAa,CAAC,CAMtB;AAED,2BAA2B;AAC3B,wBAAgB,KAAK,CAAC,GAAG,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,KAAK,CAAC,SAAS,CAAC,CAElE;AAED,4DAA4D;AAC5D,wBAAgB,GAAG,CAAC,GAAG,KAAK,EAAE,SAAS,IAAI,EAAE,GAAG,KAAK,CAAC,OAAO,CAAC,CAE7D;AAED,mDAAmD;AACnD,wBAAgB,GAAG,CAAC,GAAG,KAAK,EAAE,SAAS,IAAI,EAAE,GAAG,KAAK,CAAC,OAAO,CAAC,CAE7D;AAED,uCAAuC;AACvC,wBAAgB,GAAG,CAAC,IAAI,EAAE,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,CAE9C;AAED,oDAAoD;AACpD,wBAAgB,MAAM,CAAC,CAAC,SAAS,cAAc,GAAG,SAAS,GAAG,aAAa,EACzE,IAAI,EAAE,CAAC,EACP,IAAI,EAAE,MAAM,GACX,KAAK,CAAC,CAAC,CAAC,CAEV"}
package/dist/build.js ADDED
@@ -0,0 +1,77 @@
1
+ /**
2
+ * A readable way to write a rule, which is also the rule.
3
+ *
4
+ * `weekdays().and(timeOfDay("09:00", "17:00"))` reads better than the nested
5
+ * object literal it stands for — and it *is* that object literal, with methods
6
+ * hanging off it. `JSON.stringify` omits function-valued properties, so a built
7
+ * rule serialises to exactly the document a hand-written one would, with no
8
+ * `.build()` step and nothing to unwrap.
9
+ *
10
+ * That is the whole trick: there is no builder type to convert out of, because
11
+ * a built rule already satisfies `Rule`.
12
+ */
13
+ function build(node) {
14
+ const self = {
15
+ ...node,
16
+ and: (...others) => build({ type: "all", rules: [self, ...others] }),
17
+ or: (...others) => build({ type: "any", rules: [self, ...others] }),
18
+ except: (...others) => build({
19
+ type: "all",
20
+ rules: [
21
+ self,
22
+ { type: "not", rule: { type: "any", rules: [...others] } },
23
+ ],
24
+ }),
25
+ };
26
+ return self;
27
+ }
28
+ /** All of time. */
29
+ export function always() {
30
+ return build({ type: "always" });
31
+ }
32
+ /** No time at all. */
33
+ export function never() {
34
+ return build({ type: "never" });
35
+ }
36
+ /** Whole days, by day of the week. */
37
+ export function daysOfWeek(...days) {
38
+ return build({ type: "daysOfWeek", days });
39
+ }
40
+ /** Monday to Friday. */
41
+ export function weekdays() {
42
+ return daysOfWeek("monday", "tuesday", "wednesday", "thursday", "friday");
43
+ }
44
+ /** Saturday and Sunday. */
45
+ export function weekends() {
46
+ return daysOfWeek("saturday", "sunday");
47
+ }
48
+ /**
49
+ * A wall-clock window within each day. A `to` earlier than `from` wraps past
50
+ * midnight, so `timeOfDay("22:00", "06:00")` is a night shift.
51
+ */
52
+ export function timeOfDay(from, to, zone) {
53
+ return build(zone === undefined
54
+ ? { type: "timeOfDay", from, to }
55
+ : { type: "timeOfDay", from, to, zone });
56
+ }
57
+ /** Whole days, by date. */
58
+ export function dates(...days) {
59
+ return build({ type: "dates", dates: days });
60
+ }
61
+ /** Every one of these must hold. With none, all of time. */
62
+ export function all(...rules) {
63
+ return build({ type: "all", rules });
64
+ }
65
+ /** Any one of these. With none, no time at all. */
66
+ export function any(...rules) {
67
+ return build({ type: "any", rules });
68
+ }
69
+ /** The times a rule does not cover. */
70
+ export function not(rule) {
71
+ return build({ type: "not", rule });
72
+ }
73
+ /** Whole days, by the zone a day is measured in. */
74
+ export function inZone(rule, zone) {
75
+ return build({ ...rule, zone });
76
+ }
77
+ //# sourceMappingURL=build.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build.js","sourceRoot":"","sources":["../src/build.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Layers, and what holds inside them.
3
+ *
4
+ * A `Rule` says *when*. A `Cascade<V>` says *what holds when*: an ordered list
5
+ * of layers, each pairing a scope with what applies inside it, resolved like
6
+ * the rules in a stylesheet — the last layer to claim a moment wins.
7
+ *
8
+ * Keeping values here rather than on `Rule` is what lets the set algebra stay
9
+ * simple. If a rule carried a value then every combinator would be generic in
10
+ * it, and `not` would have to answer what the complement of a rota is. Values
11
+ * only appear where they are actually needed, which is assignment.
12
+ */
13
+ import type { Interval } from "./interval.js";
14
+ import type { Rule } from "./rule.js";
15
+ /**
16
+ * An interval with a value assigned to it.
17
+ *
18
+ * Extends `Interval`, so everything that reads an interval — `duration`,
19
+ * `contains`, `isEmpty` — reads one of these unchanged.
20
+ */
21
+ export interface Valued<V> extends Interval {
22
+ readonly value: V;
23
+ }
24
+ /**
25
+ * One layer: where it applies, and what applies there.
26
+ *
27
+ * The two forms are separate fields rather than one field holding either,
28
+ * because with one field the resolver would have to decide which meaning a
29
+ * value carries by inspecting the shape of the caller's own domain type — and
30
+ * for a `Cascade<Rule>` the two are indistinguishable.
31
+ */
32
+ export type Layer<V> = ConstantLayer<V> | ReplacingLayer<V>;
33
+ /** A layer assigning one value across the whole of its scope. */
34
+ export interface ConstantLayer<V> {
35
+ readonly scope: Rule;
36
+ readonly value: V;
37
+ }
38
+ /**
39
+ * A layer whose scope is claimed, but whose value inside it comes from another
40
+ * cascade.
41
+ *
42
+ * This is the case a plain value cannot express. "On the eleventh we close at
43
+ * three" is not `(scope: the eleventh, value: closed)`, which would shut the
44
+ * whole day, and writing it as a value over 15:00–17:00 forces the author to
45
+ * know the hours it is overriding — which is the thing a cascade exists to
46
+ * avoid. What it means is: *within this scope, ignore the layers below and use
47
+ * this instead.*
48
+ */
49
+ export interface ReplacingLayer<V> {
50
+ readonly scope: Rule;
51
+ readonly replace: Cascade<V>;
52
+ }
53
+ /**
54
+ * An ordered list of layers. Later layers win.
55
+ *
56
+ * Order is part of the meaning, so the JSON is an array and reordering it
57
+ * changes the answer.
58
+ */
59
+ export interface Cascade<V> {
60
+ readonly type: "cascade";
61
+ readonly layers: readonly Layer<V>[];
62
+ }
63
+ /** Whether a value is a cascade rather than a rule. */
64
+ export declare function isCascade<V>(value: Rule | Cascade<V>): value is Cascade<V>;
65
+ /** An ordered list of layers, lowest priority first. */
66
+ export declare function cascade<V>(...layers: readonly Layer<V>[]): Cascade<V>;
67
+ /** One value, across the whole of a scope. */
68
+ export declare function layer<V>(scope: Rule, value: V): ConstantLayer<V>;
69
+ /**
70
+ * True while a rule holds, and unassigned everywhere else.
71
+ *
72
+ * The bridge from *when* to *what*: it is how a plain schedule becomes a
73
+ * cascade, and what {@link replace} lifts a bare rule with.
74
+ */
75
+ export declare function whenever(rule: Rule): Cascade<boolean>;
76
+ /**
77
+ * A scope claimed outright, with what holds inside it given by another
78
+ * cascade — or, for a schedule, by a rule.
79
+ *
80
+ * The rule form is sugar: it stores the lifted cascade, so the document is the
81
+ * same either way and a stored layer never needs a reader to know which form
82
+ * was written.
83
+ */
84
+ export declare function replace<V>(scope: Rule, replacement: Cascade<V>): ReplacingLayer<V>;
85
+ export declare function replace(scope: Rule, replacement: Rule): ReplacingLayer<boolean>;
86
+ //# sourceMappingURL=cascade.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cascade.d.ts","sourceRoot":"","sources":["../src/cascade.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEtC;;;;;GAKG;AACH,MAAM,WAAW,MAAM,CAAC,CAAC,CAAE,SAAQ,QAAQ;IACzC,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC;CACnB;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,KAAK,CAAC,CAAC,IAAI,aAAa,CAAC,CAAC,CAAC,GAAG,cAAc,CAAC,CAAC,CAAC,CAAC;AAE5D,iEAAiE;AACjE,MAAM,WAAW,aAAa,CAAC,CAAC;IAC9B,QAAQ,CAAC,KAAK,EAAE,IAAI,CAAC;IACrB,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC;CACnB;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,cAAc,CAAC,CAAC;IAC/B,QAAQ,CAAC,KAAK,EAAE,IAAI,CAAC;IACrB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;CAC9B;AAED;;;;;GAKG;AACH,MAAM,WAAW,OAAO,CAAC,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,SAAS,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;CACtC;AAED,uDAAuD;AACvD,wBAAgB,SAAS,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,KAAK,IAAI,OAAO,CAAC,CAAC,CAAC,CAE1E;AAED,wDAAwD;AACxD,wBAAgB,OAAO,CAAC,CAAC,EAAE,GAAG,MAAM,EAAE,SAAS,KAAK,CAAC,CAAC,CAAC,EAAE,GAAG,OAAO,CAAC,CAAC,CAAC,CAErE;AAED,8CAA8C;AAC9C,wBAAgB,KAAK,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,GAAG,aAAa,CAAC,CAAC,CAAC,CAEhE;AAED;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,CAErD;AAED;;;;;;;GAOG;AACH,wBAAgB,OAAO,CAAC,CAAC,EACvB,KAAK,EAAE,IAAI,EACX,WAAW,EAAE,OAAO,CAAC,CAAC,CAAC,GACtB,cAAc,CAAC,CAAC,CAAC,CAAC;AACrB,wBAAgB,OAAO,CACrB,KAAK,EAAE,IAAI,EACX,WAAW,EAAE,IAAI,GAChB,cAAc,CAAC,OAAO,CAAC,CAAC"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Layers, and what holds inside them.
3
+ *
4
+ * A `Rule` says *when*. A `Cascade<V>` says *what holds when*: an ordered list
5
+ * of layers, each pairing a scope with what applies inside it, resolved like
6
+ * the rules in a stylesheet — the last layer to claim a moment wins.
7
+ *
8
+ * Keeping values here rather than on `Rule` is what lets the set algebra stay
9
+ * simple. If a rule carried a value then every combinator would be generic in
10
+ * it, and `not` would have to answer what the complement of a rota is. Values
11
+ * only appear where they are actually needed, which is assignment.
12
+ */
13
+ /** Whether a value is a cascade rather than a rule. */
14
+ export function isCascade(value) {
15
+ return value.type === "cascade";
16
+ }
17
+ /** An ordered list of layers, lowest priority first. */
18
+ export function cascade(...layers) {
19
+ return { type: "cascade", layers };
20
+ }
21
+ /** One value, across the whole of a scope. */
22
+ export function layer(scope, value) {
23
+ return { scope, value };
24
+ }
25
+ /**
26
+ * True while a rule holds, and unassigned everywhere else.
27
+ *
28
+ * The bridge from *when* to *what*: it is how a plain schedule becomes a
29
+ * cascade, and what {@link replace} lifts a bare rule with.
30
+ */
31
+ export function whenever(rule) {
32
+ return cascade(layer(rule, true));
33
+ }
34
+ export function replace(scope, replacement) {
35
+ return {
36
+ scope,
37
+ replace: isCascade(replacement) ? replacement : whenever(replacement),
38
+ };
39
+ }
40
+ //# sourceMappingURL=cascade.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cascade.js","sourceRoot":"","sources":["../src/cascade.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AA0DH,uDAAuD;AACvD,MAAM,UAAU,SAAS,CAAI,KAAwB;IACnD,OAAO,KAAK,CAAC,IAAI,KAAK,SAAS,CAAC;AAClC,CAAC;AAED,wDAAwD;AACxD,MAAM,UAAU,OAAO,CAAI,GAAG,MAA2B;IACvD,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC;AACrC,CAAC;AAED,8CAA8C;AAC9C,MAAM,UAAU,KAAK,CAAI,KAAW,EAAE,KAAQ;IAC5C,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;AAC1B,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,QAAQ,CAAC,IAAU;IACjC,OAAO,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;AACpC,CAAC;AAkBD,MAAM,UAAU,OAAO,CACrB,KAAW,EACX,WAAoC;IAEpC,OAAO;QACL,KAAK;QACL,OAAO,EAAE,SAAS,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC;KACtE,CAAC;AACJ,CAAC"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * What a rule is evaluated against.
3
+ *
4
+ * An object rather than a bare window, because rules need more than a window
5
+ * and always will: a sunrise rule needs coordinates, a rule written in the
6
+ * Hebrew calendar needs the calendar, a rendered description needs the locale.
7
+ * Adding a field here is harmless; changing a bare parameter into an object
8
+ * later would break every rule implementation, including anyone else's.
9
+ */
10
+ export interface Context {
11
+ /**
12
+ * Where evaluation begins, and — since a `ZonedDateTime` carries one — the
13
+ * time zone any rule that does not name its own is read in. One source of
14
+ * truth: there is no way for a separate `zone` field to disagree with this.
15
+ */
16
+ readonly from: Temporal.ZonedDateTime;
17
+ /**
18
+ * Where evaluation stops. Optional, because a recurrence genuinely has no
19
+ * end and pretending otherwise would have callers guessing a window big
20
+ * enough to hold an answer they cannot predict.
21
+ *
22
+ * Leaving it out means the streams a rule produces may be endless. That is
23
+ * supported and sometimes what you want — `take(…, 3)` over an endless
24
+ * stream is exact and cheap — but a composition whose answer is empty then
25
+ * has nothing to discover that from, and will not finish. Bound the window
26
+ * when the answer might be empty.
27
+ */
28
+ readonly to?: Temporal.ZonedDateTime;
29
+ /** Where on Earth, for rules about the sun, the moon or the tide. */
30
+ readonly location?: {
31
+ readonly latitude: number;
32
+ readonly longitude: number;
33
+ };
34
+ /** For rendering a rule as text, and for locale-specific conventions. */
35
+ readonly locale?: string;
36
+ }
37
+ /** The context's window, in the form the interval algebra takes. */
38
+ export declare function windowOf(context: Context): {
39
+ readonly start: Temporal.ZonedDateTime;
40
+ readonly end: Temporal.ZonedDateTime | undefined;
41
+ };
42
+ /** The zone a rule is read in when it does not name one of its own. */
43
+ export declare function zoneOf(context: Context, override?: string): string;
44
+ //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,MAAM,WAAW,OAAO;IACtB;;;;OAIG;IACH,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,aAAa,CAAC;IAEtC;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,EAAE,CAAC,EAAE,QAAQ,CAAC,aAAa,CAAC;IAErC,qEAAqE;IACrE,QAAQ,CAAC,QAAQ,CAAC,EAAE;QAClB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;QAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;KAC5B,CAAC;IAEF,yEAAyE;IACzE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,oEAAoE;AACpE,wBAAgB,QAAQ,CAAC,OAAO,EAAE,OAAO,GAAG;IAC1C,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,aAAa,CAAC;IACvC,QAAQ,CAAC,GAAG,EAAE,QAAQ,CAAC,aAAa,GAAG,SAAS,CAAC;CAClD,CAEA;AAED,uEAAuE;AACvE,wBAAgB,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAElE"}
@@ -0,0 +1,9 @@
1
+ /** The context's window, in the form the interval algebra takes. */
2
+ export function windowOf(context) {
3
+ return { start: context.from, end: context.to };
4
+ }
5
+ /** The zone a rule is read in when it does not name one of its own. */
6
+ export function zoneOf(context, override) {
7
+ return override ?? context.from.timeZoneId;
8
+ }
9
+ //# sourceMappingURL=context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAwCA,oEAAoE;AACpE,MAAM,UAAU,QAAQ,CAAC,OAAgB;IAIvC,OAAO,EAAE,KAAK,EAAE,OAAO,CAAC,IAAI,EAAE,GAAG,EAAE,OAAO,CAAC,EAAE,EAAE,CAAC;AAClD,CAAC;AAED,uEAAuE;AACvE,MAAM,UAAU,MAAM,CAAC,OAAgB,EAAE,QAAiB;IACxD,OAAO,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC;AAC7C,CAAC"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Rules that select whole days: by weekday, and by date.
3
+ *
4
+ * Both uphold the stream contract — sorted, not overlapping, coalesced — and
5
+ * the coalescing is the fiddly part. Two selected days that happen to be
6
+ * consecutive are one interval, not two that touch at midnight, and a stream of
7
+ * touching intervals is one the sweeps in `interval-stream.ts` read wrongly.
8
+ */
9
+ import { type Context } from "./context.js";
10
+ import type { IntervalStream } from "./interval-stream.js";
11
+ import { type Weekday } from "./rule.js";
12
+ /** Whole days selected by day of the week. */
13
+ export declare function weekdayIntervals(context: Context, days: readonly Weekday[], zone?: string): IntervalStream;
14
+ /**
15
+ * Whole days named by date.
16
+ *
17
+ * Walks the given dates rather than the calendar, so a handful of dates costs a
18
+ * handful of steps however far apart they are, and the stream ends when they
19
+ * do. Sorted and de-duplicated first, because the contract is about the output
20
+ * and callers write dates in whatever order they think of them.
21
+ */
22
+ export declare function dateIntervals(context: Context, dates: readonly string[], zone?: string): IntervalStream;
23
+ //# sourceMappingURL=day-rules.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"day-rules.d.ts","sourceRoot":"","sources":["../src/day-rules.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,KAAK,OAAO,EAAU,MAAM,cAAc,CAAC;AACpD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EAAY,KAAK,OAAO,EAAE,MAAM,WAAW,CAAC;AAsDnD,8CAA8C;AAC9C,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,SAAS,OAAO,EAAE,EACxB,IAAI,CAAC,EAAE,MAAM,GACZ,cAAc,CAehB;AAED;;;;;;;GAOG;AACH,wBAAiB,aAAa,CAC5B,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,SAAS,MAAM,EAAE,EACxB,IAAI,CAAC,EAAE,MAAM,GACZ,cAAc,CAiChB"}
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Rules that select whole days: by weekday, and by date.
3
+ *
4
+ * Both uphold the stream contract — sorted, not overlapping, coalesced — and
5
+ * the coalescing is the fiddly part. Two selected days that happen to be
6
+ * consecutive are one interval, not two that touch at midnight, and a stream of
7
+ * touching intervals is one the sweeps in `interval-stream.ts` read wrongly.
8
+ */
9
+ import { zoneOf } from "./context.js";
10
+ import { WEEKDAYS } from "./rule.js";
11
+ function startOfDay(date, zone) {
12
+ return date.toZonedDateTime({ timeZone: zone, plainTime: "00:00" });
13
+ }
14
+ function weekdayOf(date) {
15
+ return WEEKDAYS[date.dayOfWeek - 1];
16
+ }
17
+ /**
18
+ * Whole days matching a predicate, walked forward from the context, with runs
19
+ * of consecutive matches merged into one interval.
20
+ *
21
+ * Endless unless the context bounds it. The run being built is flushed when the
22
+ * window ends, so a rule that matches every day still terminates — without
23
+ * that, a run that never closes would never yield anything at all.
24
+ */
25
+ function* matchingDays(context, zone, matches) {
26
+ const stop = context.to;
27
+ let date = context.from.withTimeZone(zone).toPlainDate();
28
+ let runStart;
29
+ for (;;) {
30
+ const dayStart = startOfDay(date, zone);
31
+ if (stop !== undefined &&
32
+ Temporal.ZonedDateTime.compare(dayStart, stop) >= 0) {
33
+ if (runStart !== undefined) {
34
+ yield { start: startOfDay(runStart, zone), end: dayStart };
35
+ }
36
+ return;
37
+ }
38
+ if (matches(date)) {
39
+ runStart ??= date;
40
+ }
41
+ else if (runStart !== undefined) {
42
+ yield { start: startOfDay(runStart, zone), end: dayStart };
43
+ runStart = undefined;
44
+ }
45
+ date = date.add({ days: 1 });
46
+ }
47
+ }
48
+ /** Whole days selected by day of the week. */
49
+ export function weekdayIntervals(context, days, zone) {
50
+ const wanted = new Set(days);
51
+ // Nothing can match, so there is nothing to walk the calendar for. Without
52
+ // this, an unbounded context sends `matchingDays` forward a day at a time
53
+ // until Temporal's year limit, thousands of centuries later, and reports it
54
+ // as a date range error rather than as the empty rule it is.
55
+ if (wanted.size === 0) {
56
+ return [];
57
+ }
58
+ return matchingDays(context, zoneOf(context, zone), (date) => {
59
+ const weekday = weekdayOf(date);
60
+ return weekday !== undefined && wanted.has(weekday);
61
+ });
62
+ }
63
+ /**
64
+ * Whole days named by date.
65
+ *
66
+ * Walks the given dates rather than the calendar, so a handful of dates costs a
67
+ * handful of steps however far apart they are, and the stream ends when they
68
+ * do. Sorted and de-duplicated first, because the contract is about the output
69
+ * and callers write dates in whatever order they think of them.
70
+ */
71
+ export function* dateIntervals(context, dates, zone) {
72
+ const inZone = zoneOf(context, zone);
73
+ const days = [
74
+ ...new Set(dates.map((date) => Temporal.PlainDate.from(date).toString())),
75
+ ]
76
+ .toSorted()
77
+ .map((date) => Temporal.PlainDate.from(date));
78
+ let runStart;
79
+ let runEnd;
80
+ for (const day of days) {
81
+ if (runEnd !== undefined && Temporal.PlainDate.compare(day, runEnd) === 0) {
82
+ // Consecutive with the run so far, so it extends rather than starts one.
83
+ runEnd = day.add({ days: 1 });
84
+ continue;
85
+ }
86
+ if (runStart !== undefined && runEnd !== undefined) {
87
+ yield {
88
+ start: startOfDay(runStart, inZone),
89
+ end: startOfDay(runEnd, inZone),
90
+ };
91
+ }
92
+ runStart = day;
93
+ runEnd = day.add({ days: 1 });
94
+ }
95
+ if (runStart !== undefined && runEnd !== undefined) {
96
+ yield {
97
+ start: startOfDay(runStart, inZone),
98
+ end: startOfDay(runEnd, inZone),
99
+ };
100
+ }
101
+ }
102
+ //# sourceMappingURL=day-rules.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"day-rules.js","sourceRoot":"","sources":["../src/day-rules.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -2,8 +2,14 @@
2
2
  * Quando: declarative temporal rules for schedules, deadlines, constraints and
3
3
  * exceptions.
4
4
  *
5
- * What exists so far is the interval core the algebra everything else is
6
- * built from. Rules, cascades and queries are still to come.
5
+ * What exists so far is the interval core, the rule language on top of it, and
6
+ * the two ends of "rules are data": a builder that writes one, and a parser
7
+ * that reads one back from whatever a database or a form actually held.
8
+ * Queries sit on top: is it open now, how much working time is in this window,
9
+ * when does it next open, and where do you get to after three hours that only
10
+ * count while it is open. Cascades sit beside them: ordered layers that carry
11
+ * values, resolved by precedence, for the questions a boolean schedule cannot
12
+ * answer.
7
13
  *
8
14
  * Requires a runtime with `Temporal`: Node 26 or later, or a browser that
9
15
  * implements it.
@@ -16,4 +22,22 @@ export { compareEnds, compareStarts, contains, duration, isEmpty, startsAtOrBefo
16
22
  export type { IntervalStream } from "./interval-stream.js";
17
23
  export { clip, complement, intersect, union } from "./interval-stream.js";
18
24
  export { take } from "./stream.js";
25
+ export type { Context } from "./context.js";
26
+ export type { AllRule, AnyRule, AlwaysRule, DatesRule, DaysOfWeekRule, NeverRule, NotRule, Rule, TimeOfDayRule, Weekday, } from "./rule.js";
27
+ export { WEEKDAYS } from "./rule.js";
28
+ export { intervals } from "./interpret.js";
29
+ export type { Built } from "./build.js";
30
+ export { all, always, any, dates, daysOfWeek, inZone, never, not, timeOfDay, weekdays, weekends, } from "./build.js";
31
+ export { parseRule } from "./parse.js";
32
+ export type { Cascade, ConstantLayer, Layer, ReplacingLayer, Valued, } from "./cascade.js";
33
+ export { cascade, isCascade, layer, replace, whenever } from "./cascade.js";
34
+ export type { ValuedStream } from "./valued-stream.js";
35
+ export { resolve } from "./resolve.js";
36
+ export type { PlainRule } from "./plain-forms.js";
37
+ export type { Schedule } from "./schedule.js";
38
+ export { schedule } from "./schedule.js";
39
+ export type { Rota } from "./rota.js";
40
+ export { rota } from "./rota.js";
41
+ export type { Search } from "./query.js";
42
+ export { activeAt, advanceBy, elapsed, next } from "./query.js";
19
43
  //# sourceMappingURL=index.d.ts.map