@kensio/quando 0.1.0 → 1.0.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 (97) hide show
  1. package/README.md +135 -4
  2. package/dist/assigned.d.ts +68 -0
  3. package/dist/assigned.d.ts.map +1 -0
  4. package/dist/assigned.js +84 -0
  5. package/dist/assigned.js.map +1 -0
  6. package/dist/build.d.ts +54 -0
  7. package/dist/build.d.ts.map +1 -0
  8. package/dist/build.js +77 -0
  9. package/dist/build.js.map +1 -0
  10. package/dist/canonical-rule.d.ts +23 -0
  11. package/dist/canonical-rule.d.ts.map +1 -0
  12. package/dist/canonical-rule.js +139 -0
  13. package/dist/canonical-rule.js.map +1 -0
  14. package/dist/canonical.d.ts +44 -0
  15. package/dist/canonical.d.ts.map +1 -0
  16. package/dist/canonical.js +53 -0
  17. package/dist/canonical.js.map +1 -0
  18. package/dist/cascade.d.ts +107 -0
  19. package/dist/cascade.d.ts.map +1 -0
  20. package/dist/cascade.js +53 -0
  21. package/dist/cascade.js.map +1 -0
  22. package/dist/context.d.ts +44 -0
  23. package/dist/context.d.ts.map +1 -0
  24. package/dist/context.js +9 -0
  25. package/dist/context.js.map +1 -0
  26. package/dist/day-rules.d.ts +23 -0
  27. package/dist/day-rules.d.ts.map +1 -0
  28. package/dist/day-rules.js +102 -0
  29. package/dist/day-rules.js.map +1 -0
  30. package/dist/index.d.ts +37 -2
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +24 -2
  33. package/dist/index.js.map +1 -1
  34. package/dist/interpret.d.ts +31 -0
  35. package/dist/interpret.d.ts.map +1 -0
  36. package/dist/interpret.js +99 -0
  37. package/dist/interpret.js.map +1 -0
  38. package/dist/interval-stream.d.ts +11 -4
  39. package/dist/interval-stream.d.ts.map +1 -1
  40. package/dist/interval-stream.js.map +1 -1
  41. package/dist/merge.d.ts +43 -0
  42. package/dist/merge.d.ts.map +1 -0
  43. package/dist/merge.js +79 -0
  44. package/dist/merge.js.map +1 -0
  45. package/dist/parse-cascade.d.ts +37 -0
  46. package/dist/parse-cascade.d.ts.map +1 -0
  47. package/dist/parse-cascade.js +105 -0
  48. package/dist/parse-cascade.js.map +1 -0
  49. package/dist/parse-fields.d.ts +27 -0
  50. package/dist/parse-fields.d.ts.map +1 -0
  51. package/dist/parse-fields.js +65 -0
  52. package/dist/parse-fields.js.map +1 -0
  53. package/dist/parse-shape.d.ts +39 -0
  54. package/dist/parse-shape.d.ts.map +1 -0
  55. package/dist/parse-shape.js +77 -0
  56. package/dist/parse-shape.js.map +1 -0
  57. package/dist/parse.d.ts +22 -0
  58. package/dist/parse.d.ts.map +1 -0
  59. package/dist/parse.js +93 -0
  60. package/dist/parse.js.map +1 -0
  61. package/dist/plain-forms.d.ts +22 -0
  62. package/dist/plain-forms.d.ts.map +1 -0
  63. package/dist/plain-forms.js +54 -0
  64. package/dist/plain-forms.js.map +1 -0
  65. package/dist/query.d.ts +73 -0
  66. package/dist/query.d.ts.map +1 -0
  67. package/dist/query.js +127 -0
  68. package/dist/query.js.map +1 -0
  69. package/dist/resolve.d.ts +31 -0
  70. package/dist/resolve.d.ts.map +1 -0
  71. package/dist/resolve.js +95 -0
  72. package/dist/resolve.js.map +1 -0
  73. package/dist/rota.d.ts +49 -0
  74. package/dist/rota.d.ts.map +1 -0
  75. package/dist/rota.js +41 -0
  76. package/dist/rota.js.map +1 -0
  77. package/dist/rule.d.ts +73 -0
  78. package/dist/rule.d.ts.map +1 -0
  79. package/dist/rule.js +20 -0
  80. package/dist/rule.js.map +1 -0
  81. package/dist/schedule.d.ts +69 -0
  82. package/dist/schedule.d.ts.map +1 -0
  83. package/dist/schedule.js +80 -0
  84. package/dist/schedule.js.map +1 -0
  85. package/dist/tally.d.ts +76 -0
  86. package/dist/tally.d.ts.map +1 -0
  87. package/dist/tally.js +80 -0
  88. package/dist/tally.js.map +1 -0
  89. package/dist/time-rules.d.ts +18 -0
  90. package/dist/time-rules.d.ts.map +1 -0
  91. package/dist/time-rules.js +55 -0
  92. package/dist/time-rules.js.map +1 -0
  93. package/dist/valued-stream.d.ts +51 -0
  94. package/dist/valued-stream.d.ts.map +1 -0
  95. package/dist/valued-stream.js +148 -0
  96. package/dist/valued-stream.js.map +1 -0
  97. package/package.json +22 -20
package/README.md CHANGED
@@ -2,8 +2,139 @@
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
+ ## A rule
21
+
22
+ ```ts
23
+ import { timeOfDay, weekdays } from "@kensio/quando";
24
+
25
+ const openingHours = weekdays().and(timeOfDay("09:00", "17:00"));
26
+ ```
27
+
28
+ There is no build step to that: it is already the rule, a plain JSON-shaped
29
+ object with methods hanging off it. `JSON.stringify` gives you a document you
30
+ can store, and `parseRule` gives you the rule back.
31
+
32
+ ## Four questions
33
+
34
+ ```ts
35
+ import { activeAt, advanceBy, elapsed, next } from "@kensio/quando";
36
+
37
+ const friday = Temporal.ZonedDateTime.from("2026-03-13T16:55[Europe/London]");
38
+
39
+ // Is it open?
40
+ activeAt(openingHours, friday);
41
+ // → true
42
+
43
+ // When does it next open?
44
+ next(openingHours, { from: friday.add({ hours: 2 }) })?.start?.toString();
45
+ // → 2026-03-16T09:00:00+00:00[Europe/London]
46
+
47
+ // How much opening is there this week?
48
+ elapsed(openingHours, {
49
+ from: Temporal.ZonedDateTime.from("2026-03-09T00:00[Europe/London]"),
50
+ to: Temporal.ZonedDateTime.from("2026-03-16T00:00[Europe/London]"),
51
+ }).toString();
52
+ // → PT40H
53
+
54
+ // Three hours of packing that only count while it is open — when is it done?
55
+ advanceBy(friday, Temporal.Duration.from({ hours: 3 }), {
56
+ during: openingHours,
57
+ })?.toString();
58
+ // → 2026-03-16T11:55:00+00:00[Europe/London]
59
+ ```
60
+
61
+ That last one is why the library exists, and it is the one that is genuinely
62
+ awkward to do by hand.
63
+
64
+ ## Or say it the way you would say it
65
+
66
+ For opening hours and rotas there is a plainer front door, which builds the same
67
+ thing:
68
+
69
+ ```ts
70
+ import { rota, schedule, tally, weekdays, weekends } from "@kensio/quando";
71
+
72
+ const openingHours = schedule()
73
+ .open(weekdays(), "09:00-17:00")
74
+ .closed("2026-12-25")
75
+ .hoursOn("2026-03-11", "09:00-15:00"); // close early, just that day
76
+
77
+ openingHours.isOpen(friday);
78
+ // → true
79
+
80
+ const onCall = rota().assign(weekdays(), "alice").assign(weekends(), "bob");
81
+
82
+ onCall.whoIsOn(friday);
83
+ // → "alice"
84
+
85
+ const staff = tally().plus(weekdays(), 3).plus("2026-03-11", 2);
86
+
87
+ staff.at(Temporal.ZonedDateTime.from("2026-03-11T11:00[Europe/London]"));
88
+ // → 5
89
+ ```
90
+
91
+ For a schedule and a rota each line outranks the ones above it, so exceptions
92
+ read in the order you would say them. A tally is the one that adds instead,
93
+ because two teams of three on a Monday are six people. See
94
+ [schedules and rotas](docs/schedules/) and [merging](docs/merging/).
95
+
96
+ ## Why intervals
97
+
98
+ A rule does not answer about an instant — it produces the intervals over which
99
+ it holds. Anything that samples has to choose a step size, trading correctness
100
+ against speed, and every derived question becomes the same loop. Producing
101
+ intervals turns those loops into arithmetic: working time in a window is a sum,
102
+ three working days from now is a walk, and a rule that can never be satisfied
103
+ yields nothing instead of never finishing.
104
+
105
+ [Concepts](docs/concepts/) has the long version.
106
+
107
+ ## Documentation
108
+
109
+ [quandojs.dev](https://quandojs.dev), which is built from [`docs/`](docs/) in
110
+ this repository:
111
+
112
+ - [Getting started](docs/getting-started/) — requirements, install, first query
113
+ - [Concepts](docs/concepts/) — the model, and why it is that shape
114
+ - [Rules](docs/rules/) — every rule type, and what each produces
115
+ - [Queries](docs/queries/) — the four questions, and termination
116
+ - [Time zones](docs/time-zones/) — wall clock against elapsed time
117
+ - [Serialisation](docs/serialisation/) — the JSON form and its boundary
118
+ - [Schedules and rotas](docs/schedules/) — opening hours and who is on, plainly
119
+ - [Cascades](docs/cascades/) — layers carrying values, resolved by precedence
120
+ - [Merging](docs/merging/) — overlap that adds rather than displaces
121
+ - [Comparing](docs/comparing/) — canonical form, equality and cache keys
122
+ - [API](docs/api/) — everything the package exports
123
+
124
+ ## What it is not
125
+
126
+ Not a date library — `Temporal` is that, and Quando is built on it. Not a
127
+ scheduler: Quando calculates _when_ and never fires anything, so its answer is
128
+ your scheduler's input. Not storage, and not a holiday data provider; calendar
129
+ data belongs in satellite packages so that the core carries none.
130
+
131
+ Not a constraint solver either. Caps per window, minimum spacing and
132
+ rolling-window totals ("ninety days in any hundred and eighty") constrain a
133
+ pattern of occurrences rather than the moments in one, which is a different
134
+ problem. See [what a rule cannot say](docs/concepts/#what-a-rule-cannot-say).
135
+
136
+ Estimates, rule set diffing and a command line are designed and not yet built.
137
+
138
+ ## Licence
139
+
140
+ [Apache-2.0](LICENSE).
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Asking the four questions of a cascade rather than of a rule.
3
+ *
4
+ * `activeAt`, `elapsed`, `next` and `advanceBy` are about *when*, and a rule
5
+ * is what they read. A cascade says *what holds when*, which is a different
6
+ * question. Narrowing one to a single value turns it back into the first.
7
+ * The times a rota assigns to Alice are a stretch of when, and every question
8
+ * worth asking about a rule is worth asking about them.
9
+ *
10
+ * So {@link assigned} is what the queries take in place of a rule, and
11
+ * {@link valueAt} is the one question a rule has no version of.
12
+ */
13
+ import type { Cascade, Valued } from "./cascade.js";
14
+ import type { Context } from "./context.js";
15
+ import type { IntervalStream } from "./interval-stream.js";
16
+ import type { Rule } from "./rule.js";
17
+ /**
18
+ * A cascade narrowed to the times it assigns one value.
19
+ *
20
+ * Not a rule, and deliberately not made to look like one. A rule is a document
21
+ * that stores and travels, and this is a question asked at the point of
22
+ * asking. Building one costs nothing and evaluates nothing.
23
+ */
24
+ export interface Assigned<V> {
25
+ readonly cascade: Cascade<V>;
26
+ readonly is: V;
27
+ }
28
+ /** Either of the two things a query can read as the times it covers. */
29
+ export type Covers<V> = Rule | Assigned<V>;
30
+ /**
31
+ * The times a cascade assigns a value.
32
+ *
33
+ * ```ts
34
+ * elapsed(assigned(onCall, "alice"), week);
35
+ * advanceBy(from, threeHours, { during: assigned(onCall, "alice") });
36
+ * ```
37
+ *
38
+ * Sameness is `Object.is`, the same test {@link coalesce} uses, so a value
39
+ * matches by identity rather than by shape.
40
+ */
41
+ export declare function assigned<V>(cascade: Cascade<V>, is: V): Assigned<V>;
42
+ /**
43
+ * The times something covers, whichever of the two it is.
44
+ *
45
+ * This is the one place the queries have to know that a cascade exists, and
46
+ * what comes back either way is an ordinary interval stream, so everything
47
+ * above it stays written once.
48
+ */
49
+ export declare function covered<V>(covers: Covers<V>, context: Context): IntervalStream;
50
+ /**
51
+ * What a cascade assigns at an instant, or `undefined` where nothing does.
52
+ *
53
+ * The question a rule has no version of. `activeAt` asks whether a rule covers
54
+ * a moment, and the answer for a cascade is not whether but what.
55
+ *
56
+ * Always terminates, whatever the layers say, because it asks about the
57
+ * smallest window there is.
58
+ */
59
+ export declare function valueAt<V>(cascade: Cascade<V>, at: Temporal.ZonedDateTime, context?: Omit<Context, "from" | "to">): V | undefined;
60
+ /**
61
+ * The next stretch a cascade assigns anything at all, with its value.
62
+ *
63
+ * `next` narrowed to one value answers "when is Alice next on". This answers
64
+ * "what happens next", whatever that turns out to be, which is the question a
65
+ * timeline asks.
66
+ */
67
+ export declare function nextValue<V>(cascade: Cascade<V>, context: Context): Valued<V> | undefined;
68
+ //# sourceMappingURL=assigned.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assigned.d.ts","sourceRoot":"","sources":["../src/assigned.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AACpD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAG3D,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAGtC;;;;;;GAMG;AACH,MAAM,WAAW,QAAQ,CAAC,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;IAC7B,QAAQ,CAAC,EAAE,EAAE,CAAC,CAAC;CAChB;AAED,wEAAwE;AACxE,MAAM,MAAM,MAAM,CAAC,CAAC,IAAI,IAAI,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;AAE3C;;;;;;;;;;GAUG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAEnE;AAOD;;;;;;GAMG;AACH,wBAAgB,OAAO,CAAC,CAAC,EACvB,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,EACjB,OAAO,EAAE,OAAO,GACf,cAAc,CAKhB;AAWD;;;;;;;;GAQG;AACH,wBAAgB,OAAO,CAAC,CAAC,EACvB,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,EACnB,EAAE,EAAE,QAAQ,CAAC,aAAa,EAC1B,OAAO,CAAC,EAAE,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC,GACrC,CAAC,GAAG,SAAS,CAQf;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,CAAC,EACzB,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,EACnB,OAAO,EAAE,OAAO,GACf,MAAM,CAAC,CAAC,CAAC,GAAG,SAAS,CAGvB"}
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Asking the four questions of a cascade rather than of a rule.
3
+ *
4
+ * `activeAt`, `elapsed`, `next` and `advanceBy` are about *when*, and a rule
5
+ * is what they read. A cascade says *what holds when*, which is a different
6
+ * question. Narrowing one to a single value turns it back into the first.
7
+ * The times a rota assigns to Alice are a stretch of when, and every question
8
+ * worth asking about a rule is worth asking about them.
9
+ *
10
+ * So {@link assigned} is what the queries take in place of a rule, and
11
+ * {@link valueAt} is the one question a rule has no version of.
12
+ */
13
+ import { intervals } from "./interpret.js";
14
+ import { resolve } from "./resolve.js";
15
+ import { take } from "./stream.js";
16
+ /**
17
+ * The times a cascade assigns a value.
18
+ *
19
+ * ```ts
20
+ * elapsed(assigned(onCall, "alice"), week);
21
+ * advanceBy(from, threeHours, { during: assigned(onCall, "alice") });
22
+ * ```
23
+ *
24
+ * Sameness is `Object.is`, the same test {@link coalesce} uses, so a value
25
+ * matches by identity rather than by shape.
26
+ */
27
+ export function assigned(cascade, is) {
28
+ return { cascade, is };
29
+ }
30
+ /** Whether a query is reading a rule or a narrowed cascade. */
31
+ function isRule(covers) {
32
+ return "type" in covers;
33
+ }
34
+ /**
35
+ * The times something covers, whichever of the two it is.
36
+ *
37
+ * This is the one place the queries have to know that a cascade exists, and
38
+ * what comes back either way is an ordinary interval stream, so everything
39
+ * above it stays written once.
40
+ */
41
+ export function covered(covers, context) {
42
+ if (isRule(covers)) {
43
+ return intervals(covers, context);
44
+ }
45
+ return matching(covers, context);
46
+ }
47
+ /** The stretches of a resolved cascade carrying the value asked for. */
48
+ function* matching(covers, context) {
49
+ for (const span of resolve(covers.cascade, context)) {
50
+ if (Object.is(span.value, covers.is)) {
51
+ yield { start: span.start, end: span.end };
52
+ }
53
+ }
54
+ }
55
+ /**
56
+ * What a cascade assigns at an instant, or `undefined` where nothing does.
57
+ *
58
+ * The question a rule has no version of. `activeAt` asks whether a rule covers
59
+ * a moment, and the answer for a cascade is not whether but what.
60
+ *
61
+ * Always terminates, whatever the layers say, because it asks about the
62
+ * smallest window there is.
63
+ */
64
+ export function valueAt(cascade, at, context) {
65
+ const moment = {
66
+ ...context,
67
+ from: at,
68
+ to: at.add({ nanoseconds: 1 }),
69
+ };
70
+ const [now] = take(resolve(cascade, moment), 1);
71
+ return now?.value;
72
+ }
73
+ /**
74
+ * The next stretch a cascade assigns anything at all, with its value.
75
+ *
76
+ * `next` narrowed to one value answers "when is Alice next on". This answers
77
+ * "what happens next", whatever that turns out to be, which is the question a
78
+ * timeline asks.
79
+ */
80
+ export function nextValue(cascade, context) {
81
+ const [first] = take(resolve(cascade, context), 1);
82
+ return first;
83
+ }
84
+ //# sourceMappingURL=assigned.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assigned.js","sourceRoot":"","sources":["../src/assigned.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAKH,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvC,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAiBnC;;;;;;;;;;GAUG;AACH,MAAM,UAAU,QAAQ,CAAI,OAAmB,EAAE,EAAK;IACpD,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;AACzB,CAAC;AAED,+DAA+D;AAC/D,SAAS,MAAM,CAAI,MAAiB;IAClC,OAAO,MAAM,IAAI,MAAM,CAAC;AAC1B,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,OAAO,CACrB,MAAiB,EACjB,OAAgB;IAEhB,IAAI,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC;QACnB,OAAO,SAAS,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACpC,CAAC;IACD,OAAO,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AACnC,CAAC;AAED,wEAAwE;AACxE,QAAQ,CAAC,CAAC,QAAQ,CAAI,MAAmB,EAAE,OAAgB;IACzD,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC;QACpD,IAAI,MAAM,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC;YACrC,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC;QAC7C,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,OAAO,CACrB,OAAmB,EACnB,EAA0B,EAC1B,OAAsC;IAEtC,MAAM,MAAM,GAAY;QACtB,GAAG,OAAO;QACV,IAAI,EAAE,EAAE;QACR,EAAE,EAAE,EAAE,CAAC,GAAG,CAAC,EAAE,WAAW,EAAE,CAAC,EAAE,CAAC;KAC/B,CAAC;IACF,MAAM,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC;IAChD,OAAO,GAAG,EAAE,KAAK,CAAC;AACpB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,SAAS,CACvB,OAAmB,EACnB,OAAgB;IAEhB,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC;IACnD,OAAO,KAAK,CAAC;AACf,CAAC"}
@@ -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,23 @@
1
+ /**
2
+ * The one form of a rule that says what a rule says.
3
+ *
4
+ * `.except(…)` is `all(this, not(any(…)))` spelled out, so a rule built with
5
+ * it carries an `all` inside an `all`. Nothing is wrong with that, and it does
6
+ * mean two rules a reader would call identical are two different documents.
7
+ * Comparing them, hashing them or diffing them all want the same thing first,
8
+ * which is a form that depends on what a rule says rather than on how it was
9
+ * written.
10
+ *
11
+ * The normal form here is syntactic. It flattens, drops identities, collapses
12
+ * double negation, sorts and deduplicates, and stops. Two rules that *cover
13
+ * the same time* by different routes stay different, because deciding that in
14
+ * general means evaluating them over all of time. `always` and the seven days
15
+ * of the week are the pair to keep in mind.
16
+ *
17
+ * Nothing here throws. A rule holding a date that will not parse comes back
18
+ * with that date untouched, because a function used for cache keys is worth
19
+ * more total than strict, and `parseRule` is the place that refuses.
20
+ */
21
+ import { type Rule } from "./rule.js";
22
+ export declare function canonicalRule(rule: Rule): Rule;
23
+ //# sourceMappingURL=canonical-rule.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canonical-rule.d.ts","sourceRoot":"","sources":["../src/canonical-rule.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,KAAK,IAAI,EAA0B,MAAM,WAAW,CAAC;AAyF9D,wBAAgB,aAAa,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,CAqD9C"}
@@ -0,0 +1,139 @@
1
+ /**
2
+ * The one form of a rule that says what a rule says.
3
+ *
4
+ * `.except(…)` is `all(this, not(any(…)))` spelled out, so a rule built with
5
+ * it carries an `all` inside an `all`. Nothing is wrong with that, and it does
6
+ * mean two rules a reader would call identical are two different documents.
7
+ * Comparing them, hashing them or diffing them all want the same thing first,
8
+ * which is a form that depends on what a rule says rather than on how it was
9
+ * written.
10
+ *
11
+ * The normal form here is syntactic. It flattens, drops identities, collapses
12
+ * double negation, sorts and deduplicates, and stops. Two rules that *cover
13
+ * the same time* by different routes stay different, because deciding that in
14
+ * general means evaluating them over all of time. `always` and the seven days
15
+ * of the week are the pair to keep in mind.
16
+ *
17
+ * Nothing here throws. A rule holding a date that will not parse comes back
18
+ * with that date untouched, because a function used for cache keys is worth
19
+ * more total than strict, and `parseRule` is the place that refuses.
20
+ */
21
+ import { WEEKDAYS } from "./rule.js";
22
+ /** Where a weekday sorts. Calendar order rather than alphabetical. */
23
+ const DAY_ORDER = new Map(WEEKDAYS.map((day, index) => [day, index]));
24
+ /** A rule's stable string form, which is what sorting and equality compare. */
25
+ function key(rule) {
26
+ return JSON.stringify(rule);
27
+ }
28
+ /** Present or absent, never present-and-undefined. */
29
+ function zonePart(zone) {
30
+ return zone === undefined ? {} : { zone };
31
+ }
32
+ const asTime = (from) => Temporal.PlainTime.from(from);
33
+ const asDate = (from) => Temporal.PlainDate.from(from);
34
+ /**
35
+ * A `Temporal` value written the one way, where it can be read at all.
36
+ *
37
+ * `"09:00"` and `"09:00:00"` are the same time of day written twice, and two
38
+ * rules holding one each should compare equal. A string neither form can read
39
+ * is handed back as it came, so this stays total.
40
+ */
41
+ function written(value, read) {
42
+ try {
43
+ return read(value).toString();
44
+ }
45
+ catch {
46
+ return value;
47
+ }
48
+ }
49
+ function canonicalDays(days) {
50
+ return [...new Set(days)].toSorted((a, b) => (DAY_ORDER.get(a) ?? 0) - (DAY_ORDER.get(b) ?? 0));
51
+ }
52
+ function canonicalDates(dates) {
53
+ const one = dates.map((date) => written(date, asDate));
54
+ return [...new Set(one)].toSorted();
55
+ }
56
+ /**
57
+ * The operands of an `all` or an `any`, flattened, reduced and ordered.
58
+ *
59
+ * Both are the same shape with the two constants swapped. For `all`, `always`
60
+ * adds nothing and `never` settles it. For `any` it is the other way round.
61
+ */
62
+ function combined(type, rules) {
63
+ const absorbed = type === "all" ? "always" : "never";
64
+ const settles = type === "all" ? "never" : "always";
65
+ const flat = [];
66
+ for (const rule of rules) {
67
+ const inner = canonicalRule(rule);
68
+ if (inner.type === settles) {
69
+ return { type: settles };
70
+ }
71
+ if (inner.type === absorbed) {
72
+ continue;
73
+ }
74
+ // Already canonical, so its own operands are reduced and its type is not
75
+ // the one being flattened into unless it genuinely nests.
76
+ if (inner.type === type) {
77
+ flat.push(...inner.rules);
78
+ continue;
79
+ }
80
+ flat.push(inner);
81
+ }
82
+ const kept = [...new Map(flat.map((rule) => [key(rule), rule])).values()];
83
+ const unique = kept.toSorted((a, b) => key(a).localeCompare(key(b)));
84
+ const only = unique[0];
85
+ if (only === undefined) {
86
+ return { type: absorbed };
87
+ }
88
+ return unique.length === 1 ? only : { type, rules: unique };
89
+ }
90
+ export function canonicalRule(rule) {
91
+ switch (rule.type) {
92
+ case "always":
93
+ case "never": {
94
+ return { type: rule.type };
95
+ }
96
+ case "daysOfWeek": {
97
+ return {
98
+ type: "daysOfWeek",
99
+ days: canonicalDays(rule.days),
100
+ ...zonePart(rule.zone),
101
+ };
102
+ }
103
+ case "dates": {
104
+ return {
105
+ type: "dates",
106
+ dates: canonicalDates(rule.dates),
107
+ ...zonePart(rule.zone),
108
+ };
109
+ }
110
+ case "timeOfDay": {
111
+ return {
112
+ type: "timeOfDay",
113
+ from: written(rule.from, asTime),
114
+ to: written(rule.to, asTime),
115
+ ...zonePart(rule.zone),
116
+ };
117
+ }
118
+ case "not": {
119
+ const inner = canonicalRule(rule.rule);
120
+ // Two complements cancel, and the complement of a constant is the other
121
+ // one. Both keep a `not` out of the form where it says nothing.
122
+ if (inner.type === "not") {
123
+ return inner.rule;
124
+ }
125
+ if (inner.type === "always") {
126
+ return { type: "never" };
127
+ }
128
+ if (inner.type === "never") {
129
+ return { type: "always" };
130
+ }
131
+ return { type: "not", rule: inner };
132
+ }
133
+ case "all":
134
+ case "any": {
135
+ return combined(rule.type, rule.rules);
136
+ }
137
+ }
138
+ }
139
+ //# sourceMappingURL=canonical-rule.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canonical-rule.js","sourceRoot":"","sources":["../src/canonical-rule.ts"],"names":[],"mappings":"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"}