@kensio/quando 0.1.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,67 @@
1
+ /**
2
+ * Asking a rule a question, rather than reading the times it covers.
3
+ *
4
+ * `intervals` is the plumbing. These are what a caller actually wants: is it
5
+ * open now, how much working time is in this window, when does it next open,
6
+ * and — the one both of the libraries this evolves from were built around —
7
+ * where do you get to after three hours that only count while it is open.
8
+ *
9
+ * Durations are exact elapsed time throughout. Three operating hours means
10
+ * three real hours of opening, so a window spanning a clock change is measured
11
+ * by how long it lasted rather than by what the clock said.
12
+ */
13
+ import type { Context } from "./context.js";
14
+ import { type Interval } from "./interval.js";
15
+ import type { Rule } from "./rule.js";
16
+ /**
17
+ * How far a search runs.
18
+ *
19
+ * With neither the context's `to` nor a `within`, a search is unbounded. That
20
+ * is fine and often what you want — the first interval of a satisfiable rule
21
+ * arrives immediately, however far the rule recurs. It is only a rule that
22
+ * covers *nothing* that has no answer to give and no way to discover it, and
23
+ * that case runs until stopped. Give a bound when the answer might be nothing.
24
+ */
25
+ export interface Search {
26
+ /**
27
+ * Look no further ahead than this from where the search starts.
28
+ *
29
+ * Narrows only. A context that already ends before the horizon keeps its own
30
+ * end, because a caller who gave a window meant it.
31
+ */
32
+ readonly within?: Temporal.Duration;
33
+ }
34
+ /**
35
+ * Whether a rule covers an instant.
36
+ *
37
+ * Always terminates, whatever the rule and whatever the context: it asks about
38
+ * the smallest window there is, so nothing can walk far looking for an answer.
39
+ */
40
+ export declare function activeAt(rule: Rule, at: Temporal.ZonedDateTime, context?: Omit<Context, "from" | "to">): boolean;
41
+ /**
42
+ * How much time a rule covers within a window.
43
+ *
44
+ * Needs a window with an end, because the alternative is a number that never
45
+ * finishes being counted.
46
+ */
47
+ export declare function elapsed(rule: Rule, context: Context): Temporal.Duration;
48
+ /**
49
+ * The next stretch of time a rule covers, at or after the context's start.
50
+ *
51
+ * `undefined` when there is none within the search. If the rule is covering
52
+ * time already at the context's start, that stretch is returned clipped to
53
+ * begin there — "when does it next open" answers "it is open" rather than
54
+ * skipping to tomorrow.
55
+ */
56
+ export declare function next(rule: Rule, context: Context, search?: Search): Interval | undefined;
57
+ /**
58
+ * Where you get to after an amount of time that only counts while a rule holds.
59
+ *
60
+ * Three operating hours from an order placed at five to five on a Friday is
61
+ * some way into Monday morning, and this is the function that says where.
62
+ * `undefined` when the search runs out before the time does.
63
+ */
64
+ export declare function advanceBy(from: Temporal.ZonedDateTime, amount: Temporal.Duration, options: {
65
+ readonly during: Rule;
66
+ } & Search & Omit<Context, "from" | "to">): Temporal.ZonedDateTime | undefined;
67
+ //# sourceMappingURL=query.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../src/query.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAwB,KAAK,QAAQ,EAAE,MAAM,eAAe,CAAC;AAEpE,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAMtC;;;;;;;;GAQG;AACH,MAAM,WAAW,MAAM;IACrB;;;;;OAKG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ,CAAC,QAAQ,CAAC;CACrC;AA0CD;;;;;GAKG;AACH,wBAAgB,QAAQ,CACtB,IAAI,EAAE,IAAI,EACV,EAAE,EAAE,QAAQ,CAAC,aAAa,EAC1B,OAAO,CAAC,EAAE,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC,GACrC,OAAO,CAOT;AAED;;;;;GAKG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,GAAG,QAAQ,CAAC,QAAQ,CAevE;AAED;;;;;;;GAOG;AACH,wBAAgB,IAAI,CAClB,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,OAAO,EAChB,MAAM,CAAC,EAAE,MAAM,GACd,QAAQ,GAAG,SAAS,CAGtB;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CACvB,IAAI,EAAE,QAAQ,CAAC,aAAa,EAC5B,MAAM,EAAE,QAAQ,CAAC,QAAQ,EACzB,OAAO,EAAE;IAAE,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAA;CAAE,GAAG,MAAM,GAAG,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC,GACzE,QAAQ,CAAC,aAAa,GAAG,SAAS,CA8BpC"}
package/dist/query.js ADDED
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Asking a rule a question, rather than reading the times it covers.
3
+ *
4
+ * `intervals` is the plumbing. These are what a caller actually wants: is it
5
+ * open now, how much working time is in this window, when does it next open,
6
+ * and — the one both of the libraries this evolves from were built around —
7
+ * where do you get to after three hours that only count while it is open.
8
+ *
9
+ * Durations are exact elapsed time throughout. Three operating hours means
10
+ * three real hours of opening, so a window spanning a clock change is measured
11
+ * by how long it lasted rather than by what the clock said.
12
+ */
13
+ import { duration, earlierEnd } from "./interval.js";
14
+ import { intervals } from "./interpret.js";
15
+ import { take } from "./stream.js";
16
+ /** Zero, as a duration to accumulate onto. */
17
+ const NOTHING = Temporal.Duration.from({ seconds: 0 });
18
+ function bounded(context, search) {
19
+ const within = search?.within;
20
+ if (within === undefined) {
21
+ return context;
22
+ }
23
+ // Whichever runs out first. `within` narrows a search and must never widen
24
+ // one: a context that already ends on Saturday means the caller is not
25
+ // interested in Monday, whatever horizon the search asks for.
26
+ const horizon = context.from.add(within);
27
+ return { ...context, to: earlierEnd(context.to, horizon) ?? horizon };
28
+ }
29
+ /**
30
+ * Refuses an amount whose units do not mean one fixed length of time.
31
+ *
32
+ * A day is not 24 hours on the two mornings a year a clock changes, and a
33
+ * month is not any number of hours at all. Both halves of this function would
34
+ * otherwise disagree about that: the accounting compares durations without a
35
+ * reference point, where a day *is* 24 hours, while the final step adds to a
36
+ * `ZonedDateTime`, where it is a calendar day. `P1D` and `PT24H` would land an
37
+ * hour apart, and neither answer would be wrong enough to notice.
38
+ *
39
+ * Weeks and months do not even get that far — comparing them without a
40
+ * reference point throws, with an empty message.
41
+ */
42
+ function checkExact(amount) {
43
+ const calendar = ["years", "months", "weeks", "days"].filter((unit) => amount[unit] !== 0);
44
+ if (calendar.length > 0) {
45
+ throw new RangeError(`advanceBy() measures elapsed time, so ${amount.toString()} is ambiguous: ` +
46
+ `${calendar.join(" and ")} are calendar units, and a day is not 24 hours ` +
47
+ `on the mornings a clock changes. Give hours, minutes or seconds.`);
48
+ }
49
+ }
50
+ /**
51
+ * Whether a rule covers an instant.
52
+ *
53
+ * Always terminates, whatever the rule and whatever the context: it asks about
54
+ * the smallest window there is, so nothing can walk far looking for an answer.
55
+ */
56
+ export function activeAt(rule, at, context) {
57
+ const moment = {
58
+ ...context,
59
+ from: at,
60
+ to: at.add({ nanoseconds: 1 }),
61
+ };
62
+ return take(intervals(rule, moment), 1).length > 0;
63
+ }
64
+ /**
65
+ * How much time a rule covers within a window.
66
+ *
67
+ * Needs a window with an end, because the alternative is a number that never
68
+ * finishes being counted.
69
+ */
70
+ export function elapsed(rule, context) {
71
+ if (context.to === undefined) {
72
+ throw new RangeError("elapsed() needs a window with an end: give the context a `to`.");
73
+ }
74
+ let total = NOTHING;
75
+ for (const interval of intervals(rule, context)) {
76
+ const length = duration(interval);
77
+ if (length !== undefined) {
78
+ total = total.add(length);
79
+ }
80
+ }
81
+ return total.round({ largestUnit: "hour" });
82
+ }
83
+ /**
84
+ * The next stretch of time a rule covers, at or after the context's start.
85
+ *
86
+ * `undefined` when there is none within the search. If the rule is covering
87
+ * time already at the context's start, that stretch is returned clipped to
88
+ * begin there — "when does it next open" answers "it is open" rather than
89
+ * skipping to tomorrow.
90
+ */
91
+ export function next(rule, context, search) {
92
+ const [first] = take(intervals(rule, bounded(context, search)), 1);
93
+ return first;
94
+ }
95
+ /**
96
+ * Where you get to after an amount of time that only counts while a rule holds.
97
+ *
98
+ * Three operating hours from an order placed at five to five on a Friday is
99
+ * some way into Monday morning, and this is the function that says where.
100
+ * `undefined` when the search runs out before the time does.
101
+ */
102
+ export function advanceBy(from, amount, options) {
103
+ checkExact(amount);
104
+ if (Temporal.Duration.compare(amount, NOTHING) < 0) {
105
+ throw new RangeError(`advanceBy() cannot go backwards. Asked for ${amount.toString()}.`);
106
+ }
107
+ const { during, within, ...rest } = options;
108
+ const context = bounded({ ...rest, from }, within === undefined ? undefined : { within });
109
+ let remaining = amount;
110
+ for (const interval of intervals(during, context)) {
111
+ const length = duration(interval);
112
+ // An interval with no end has more than enough of whatever is left.
113
+ if (length === undefined ||
114
+ Temporal.Duration.compare(length, remaining) >= 0) {
115
+ return interval.start?.add(remaining);
116
+ }
117
+ remaining = remaining.subtract(length);
118
+ }
119
+ return undefined;
120
+ }
121
+ //# sourceMappingURL=query.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"query.js","sourceRoot":"","sources":["../src/query.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Reading a cascade as the values it assigns, over time.
3
+ *
4
+ * The whole of resolution is one idea: **a layer holds where its own scope
5
+ * holds and no layer above it claims the moment.** Said as a rule that is
6
+ * `all(scope, not(any(…the scopes above)))`, which the rule interpreter
7
+ * already knows how to evaluate — so precedence costs no new algebra, and the
8
+ * clipping, the zone normalisation and the laziness all come along unchanged.
9
+ *
10
+ * What comes back is only the time a cascade actually assigns. A moment no
11
+ * layer claims is absent from the stream rather than present with some empty
12
+ * value, for the same reason a rule yields only the time it covers: there is
13
+ * no such thing as the value of an unassigned moment.
14
+ */
15
+ import type { Cascade } from "./cascade.js";
16
+ import type { Context } from "./context.js";
17
+ import { type ValuedStream } from "./valued-stream.js";
18
+ /**
19
+ * The values a cascade assigns within a context, in order and coalesced.
20
+ *
21
+ * Lazy, and endless when the context has no end and the layers recur — the
22
+ * same contract `intervals` keeps, because this is built out of it.
23
+ */
24
+ export declare function resolve<V>(cascade: Cascade<V>, context: Context): ValuedStream<V>;
25
+ //# sourceMappingURL=resolve.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../src/resolve.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAS,MAAM,cAAc,CAAC;AACnD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAI5C,OAAO,EAAwB,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAE7E;;;;;GAKG;AACH,wBAAgB,OAAO,CAAC,CAAC,EACvB,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,EACnB,OAAO,EAAE,OAAO,GACf,YAAY,CAAC,CAAC,CAAC,CASjB"}
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Reading a cascade as the values it assigns, over time.
3
+ *
4
+ * The whole of resolution is one idea: **a layer holds where its own scope
5
+ * holds and no layer above it claims the moment.** Said as a rule that is
6
+ * `all(scope, not(any(…the scopes above)))`, which the rule interpreter
7
+ * already knows how to evaluate — so precedence costs no new algebra, and the
8
+ * clipping, the zone normalisation and the laziness all come along unchanged.
9
+ *
10
+ * What comes back is only the time a cascade actually assigns. A moment no
11
+ * layer claims is absent from the stream rather than present with some empty
12
+ * value, for the same reason a rule yields only the time it covers: there is
13
+ * no such thing as the value of an unassigned moment.
14
+ */
15
+ import { intervals } from "./interpret.js";
16
+ import { coalesce, interleave } from "./valued-stream.js";
17
+ /**
18
+ * The values a cascade assigns within a context, in order and coalesced.
19
+ *
20
+ * Lazy, and endless when the context has no end and the layers recur — the
21
+ * same contract `intervals` keeps, because this is built out of it.
22
+ */
23
+ export function resolve(cascade, context) {
24
+ const claimed = cascade.layers.map((layer, index) => assignments(layer, wins(layer.scope, cascade.layers.slice(index + 1)), context));
25
+ return coalesce(interleave(claimed));
26
+ }
27
+ /**
28
+ * Where a layer wins: inside its own scope, and outside every scope above it.
29
+ *
30
+ * The layers above are re-evaluated once per layer below them, which is
31
+ * quadratic in the number of layers. Layers are few and the streams are lazy,
32
+ * so this buys a precedence that needs no sweep of its own for the price of
33
+ * some repeated work over a handful of rules.
34
+ */
35
+ function wins(scope, above) {
36
+ // For the top layer this is `all(scope, not(any()))` — `any()` covers no
37
+ // time, its complement covers all of it, and the intersection is the scope
38
+ // itself. Left general rather than special-cased: one path is easier to
39
+ // trust than two, and the identities make the general one right.
40
+ return {
41
+ type: "all",
42
+ rules: [
43
+ scope,
44
+ {
45
+ type: "not",
46
+ rule: { type: "any", rules: above.map((layer) => layer.scope) },
47
+ },
48
+ ],
49
+ };
50
+ }
51
+ /** What a layer assigns, over the region it wins. */
52
+ function* assignments(layer, region, context) {
53
+ for (const interval of intervals(region, context)) {
54
+ if ("value" in layer) {
55
+ yield { ...interval, value: layer.value };
56
+ continue;
57
+ }
58
+ // A replacing layer claims the region and hands the question inwards. The
59
+ // inner cascade is resolved against the region rather than against the
60
+ // whole context, which is what stops it reaching outside the scope it
61
+ // replaces — and what makes anything it leaves unassigned stay unassigned
62
+ // rather than falling through to the layers this one outranks.
63
+ yield* resolve(layer.replace, within(context, interval));
64
+ }
65
+ }
66
+ /**
67
+ * A context narrowed to one interval.
68
+ *
69
+ * The interval came from evaluating a rule against this context, so its start
70
+ * is inside the window and never unbounded; the fallback is for the type
71
+ * rather than for a case that occurs.
72
+ */
73
+ function within(context, interval) {
74
+ const { from, to: _replaced, ...rest } = context;
75
+ const start = interval.start ?? from;
76
+ return interval.end === undefined
77
+ ? { ...rest, from: start }
78
+ : { ...rest, from: start, to: interval.end };
79
+ }
80
+ //# sourceMappingURL=resolve.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve.js","sourceRoot":"","sources":["../src/resolve.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAKH,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAE3C,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAqB,MAAM,oBAAoB,CAAC;AAE7E;;;;;GAKG;AACH,MAAM,UAAU,OAAO,CACrB,OAAmB,EACnB,OAAgB;IAEhB,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,CAClD,WAAW,CACT,KAAK,EACL,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,EAClD,OAAO,CACR,CACF,CAAC;IACF,OAAO,QAAQ,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC;AACvC,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,IAAI,CAAC,KAAW,EAAE,KAAgC;IACzD,yEAAyE;IACzE,2EAA2E;IAC3E,wEAAwE;IACxE,iEAAiE;IACjE,OAAO;QACL,IAAI,EAAE,KAAK;QACX,KAAK,EAAE;YACL,KAAK;YACL;gBACE,IAAI,EAAE,KAAK;gBACX,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE;aAChE;SACF;KACF,CAAC;AACJ,CAAC;AAED,qDAAqD;AACrD,QAAQ,CAAC,CAAC,WAAW,CACnB,KAAe,EACf,MAAY,EACZ,OAAgB;IAEhB,KAAK,MAAM,QAAQ,IAAI,SAAS,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;QAClD,IAAI,OAAO,IAAI,KAAK,EAAE,CAAC;YACrB,MAAM,EAAE,GAAG,QAAQ,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,CAAC;YAC1C,SAAS;QACX,CAAC;QAED,0EAA0E;QAC1E,uEAAuE;QACvE,sEAAsE;QACtE,0EAA0E;QAC1E,+DAA+D;QAC/D,KAAK,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC;IAC3D,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,SAAS,MAAM,CAAC,OAAgB,EAAE,QAAkB;IAClD,MAAM,EAAE,IAAI,EAAE,EAAE,EAAE,SAAS,EAAE,GAAG,IAAI,EAAE,GAAG,OAAO,CAAC;IACjD,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,IAAI,IAAI,CAAC;IAErC,OAAO,QAAQ,CAAC,GAAG,KAAK,SAAS;QAC/B,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE;QAC1B,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,QAAQ,CAAC,GAAG,EAAE,CAAC;AACjD,CAAC"}
package/dist/rota.d.ts ADDED
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Who is on, in the words people use for that.
3
+ *
4
+ * The same machinery as a [schedule](./schedule.ts) with the value left open:
5
+ * a rota assigns a person, a tariff assigns a rate, a roster assigns how many
6
+ * are working. A `Rota<V>` is a `Cascade<V>`, so the core reads it unchanged.
7
+ *
8
+ * The value type accumulates as layers are added, so a rota of two names
9
+ * answers `"alice" | "bob" | undefined` rather than `string` — which is what
10
+ * makes an exhaustive switch over who is on call actually exhaustive. Ask for
11
+ * `rota<string>()` when the names are not known up front.
12
+ */
13
+ import { type Cascade } from "./cascade.js";
14
+ import { type PlainRule } from "./plain-forms.js";
15
+ import type { ValuedStream } from "./valued-stream.js";
16
+ /** Who or what holds when, and the questions worth asking about that. */
17
+ export interface Rota<V> extends Cascade<V> {
18
+ /** These times belong to this one, unless something later says otherwise. */
19
+ readonly assign: <const W>(scope: PlainRule, value: W) => Rota<V | W>;
20
+ /**
21
+ * A swap: this day goes to this one instead.
22
+ *
23
+ * The same thing as an `assign` naming a single day — it exists because
24
+ * "Carol is swapping the eleventh" is what happened, and a rota reads better
25
+ * when the exceptions say they are exceptions.
26
+ */
27
+ readonly swap: <const W>(day: PlainRule, value: W) => Rota<V | W>;
28
+ /** Who is on at that moment, or `undefined` if nobody is. */
29
+ readonly whoIsOn: (at: Temporal.ZonedDateTime) => V | undefined;
30
+ /**
31
+ * Each stretch between two moments, and who has it.
32
+ *
33
+ * Leave `to` out for an endless run of them, which is lazy and safe to stop
34
+ * pulling from whenever you have enough.
35
+ */
36
+ readonly shifts: (from: Temporal.ZonedDateTime, to?: Temporal.ZonedDateTime) => ValuedStream<V>;
37
+ }
38
+ /**
39
+ * An empty rota: nobody is on until something says they are.
40
+ *
41
+ * ```ts
42
+ * const onCall = rota()
43
+ * .assign(weekdays(), "alice")
44
+ * .assign(weekends(), "bob")
45
+ * .swap("2026-03-11", "carol");
46
+ * ```
47
+ */
48
+ export declare function rota<V = never>(): Rota<V>;
49
+ //# sourceMappingURL=rota.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rota.d.ts","sourceRoot":"","sources":["../src/rota.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,KAAK,OAAO,EAAqB,MAAM,cAAc,CAAC;AAE/D,OAAO,EAAU,KAAK,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAG1D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEvD,yEAAyE;AACzE,MAAM,WAAW,IAAI,CAAC,CAAC,CAAE,SAAQ,OAAO,CAAC,CAAC,CAAC;IACzC,6EAA6E;IAC7E,QAAQ,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,KAAK,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAEtE;;;;;;OAMG;IACH,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,KAAK,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAElE,6DAA6D;IAC7D,QAAQ,CAAC,OAAO,EAAE,CAAC,EAAE,EAAE,QAAQ,CAAC,aAAa,KAAK,CAAC,GAAG,SAAS,CAAC;IAEhE;;;;;OAKG;IACH,QAAQ,CAAC,MAAM,EAAE,CACf,IAAI,EAAE,QAAQ,CAAC,aAAa,EAC5B,EAAE,CAAC,EAAE,QAAQ,CAAC,aAAa,KACxB,YAAY,CAAC,CAAC,CAAC,CAAC;CACtB;AA4BD;;;;;;;;;GASG;AACH,wBAAgB,IAAI,CAAC,CAAC,GAAG,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,CAEzC"}
package/dist/rota.js ADDED
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Who is on, in the words people use for that.
3
+ *
4
+ * The same machinery as a [schedule](./schedule.ts) with the value left open:
5
+ * a rota assigns a person, a tariff assigns a rate, a roster assigns how many
6
+ * are working. A `Rota<V>` is a `Cascade<V>`, so the core reads it unchanged.
7
+ *
8
+ * The value type accumulates as layers are added, so a rota of two names
9
+ * answers `"alice" | "bob" | undefined` rather than `string` — which is what
10
+ * makes an exhaustive switch over who is on call actually exhaustive. Ask for
11
+ * `rota<string>()` when the names are not known up front.
12
+ */
13
+ import { layer } from "./cascade.js";
14
+ import { asDays } from "./plain-forms.js";
15
+ import { resolve } from "./resolve.js";
16
+ import { take } from "./stream.js";
17
+ function build(layers) {
18
+ const self = {
19
+ type: "cascade",
20
+ layers,
21
+ assign: (scope, value) => build([...layers, layer(asDays(scope), value)]),
22
+ swap: (day, value) => build([...layers, layer(asDays(day), value)]),
23
+ whoIsOn: (at) => {
24
+ // The smallest window there is, so this terminates whatever the layers
25
+ // say — the same trick `activeAt` uses on a rule.
26
+ const moment = { from: at, to: at.add({ nanoseconds: 1 }) };
27
+ const [now] = take(resolve(self, moment), 1);
28
+ return now?.value;
29
+ },
30
+ shifts: (from, to) => resolve(self, to === undefined ? { from } : { from, to }),
31
+ };
32
+ return self;
33
+ }
34
+ /**
35
+ * An empty rota: nobody is on until something says they are.
36
+ *
37
+ * ```ts
38
+ * const onCall = rota()
39
+ * .assign(weekdays(), "alice")
40
+ * .assign(weekends(), "bob")
41
+ * .swap("2026-03-11", "carol");
42
+ * ```
43
+ */
44
+ export function rota() {
45
+ return build([]);
46
+ }
47
+ //# sourceMappingURL=rota.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rota.js","sourceRoot":"","sources":["../src/rota.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAA4B,KAAK,EAAE,MAAM,cAAc,CAAC;AAE/D,OAAO,EAAE,MAAM,EAAkB,MAAM,kBAAkB,CAAC;AAC1D,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAgCnC,SAAS,KAAK,CAAI,MAA2B;IAC3C,MAAM,IAAI,GAAY;QACpB,IAAI,EAAE,SAAS;QACf,MAAM;QAEN,MAAM,EAAE,CAAI,KAAgB,EAAE,KAAQ,EAAE,EAAE,CACxC,KAAK,CAAQ,CAAC,GAAG,MAAM,EAAE,KAAK,CAAQ,MAAM,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC;QAE/D,IAAI,EAAE,CAAI,GAAc,EAAE,KAAQ,EAAE,EAAE,CACpC,KAAK,CAAQ,CAAC,GAAG,MAAM,EAAE,KAAK,CAAQ,MAAM,CAAC,GAAG,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC;QAE7D,OAAO,EAAE,CAAC,EAAE,EAAE,EAAE;YACd,uEAAuE;YACvE,kDAAkD;YAClD,MAAM,MAAM,GAAY,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,GAAG,CAAC,EAAE,WAAW,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC;YACrE,MAAM,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC;YAC7C,OAAO,GAAG,EAAE,KAAK,CAAC;QACpB,CAAC;QAED,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE,CACnB,OAAO,CAAC,IAAI,EAAE,EAAE,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC;KAC5D,CAAC;IAEF,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,IAAI;IAClB,OAAO,KAAK,CAAI,EAAE,CAAC,CAAC;AACtB,CAAC"}
package/dist/rule.d.ts ADDED
@@ -0,0 +1,73 @@
1
+ /**
2
+ * The rule language, as data.
3
+ *
4
+ * A rule is a plain JSON value with a `type` tag — not an object with methods.
5
+ * That is what makes storing one, sending one over a wire, and validating one
6
+ * cost nothing: the document *is* the rule. It also means a new operation over
7
+ * rules is a new function rather than a new method on every rule type, which
8
+ * matters because there are many operations coming — evaluating, describing,
9
+ * validating, rendering, diffing — and comparatively few rule types.
10
+ */
11
+ export declare const WEEKDAYS: readonly ["monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"];
12
+ export type Weekday = (typeof WEEKDAYS)[number];
13
+ /**
14
+ * A rule says *when*, and nothing else. It is boolean: the times it covers and
15
+ * the times it does not.
16
+ *
17
+ * Values — who is on call, what the tariff is — attach to layers rather than to
18
+ * rules, which is what keeps `not` meaningful and the set algebra simple.
19
+ */
20
+ export type Rule = AlwaysRule | NeverRule | DaysOfWeekRule | TimeOfDayRule | DatesRule | AllRule | AnyRule | NotRule;
21
+ /** All time. The identity for intersection. */
22
+ export interface AlwaysRule {
23
+ readonly type: "always";
24
+ }
25
+ /** No time at all. The identity for union. */
26
+ export interface NeverRule {
27
+ readonly type: "never";
28
+ }
29
+ /** Whole days, by their day of the week. */
30
+ export interface DaysOfWeekRule {
31
+ readonly type: "daysOfWeek";
32
+ readonly days: readonly Weekday[];
33
+ /** Overrides the context's zone, for a rule about a particular place. */
34
+ readonly zone?: string;
35
+ }
36
+ /**
37
+ * A window within each day, as wall-clock times: `"09:00"` to `"17:00"`.
38
+ *
39
+ * Wall clock is what people write and what schedules mean. Across a daylight
40
+ * saving transition the elapsed length of the window changes and the clock
41
+ * times do not, which is the right way round.
42
+ *
43
+ * A `to` earlier than `from` wraps past midnight, so `"22:00"` to `"06:00"` is
44
+ * a night shift rather than nothing.
45
+ */
46
+ export interface TimeOfDayRule {
47
+ readonly type: "timeOfDay";
48
+ readonly from: string;
49
+ readonly to: string;
50
+ readonly zone?: string;
51
+ }
52
+ /** Whole days, by date: `"2026-03-14"`. */
53
+ export interface DatesRule {
54
+ readonly type: "dates";
55
+ readonly dates: readonly string[];
56
+ readonly zone?: string;
57
+ }
58
+ /** Every rule must hold: intersection. */
59
+ export interface AllRule {
60
+ readonly type: "all";
61
+ readonly rules: readonly Rule[];
62
+ }
63
+ /** At least one rule must hold: union. */
64
+ export interface AnyRule {
65
+ readonly type: "any";
66
+ readonly rules: readonly Rule[];
67
+ }
68
+ /** The times a rule does not hold: complement. */
69
+ export interface NotRule {
70
+ readonly type: "not";
71
+ readonly rule: Rule;
72
+ }
73
+ //# sourceMappingURL=rule.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rule.d.ts","sourceRoot":"","sources":["../src/rule.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,eAAO,MAAM,QAAQ,YACnB,QAAQ,EACR,SAAS,EACT,WAAW,EACX,UAAU,EACV,QAAQ,EACR,UAAU,EACV,QAAQ,CACA,CAAC;AAEX,MAAM,MAAM,OAAO,GAAG,CAAC,OAAO,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC;AAEhD;;;;;;GAMG;AACH,MAAM,MAAM,IAAI,GACZ,UAAU,GACV,SAAS,GACT,cAAc,GACd,aAAa,GACb,SAAS,GACT,OAAO,GACP,OAAO,GACP,OAAO,CAAC;AAEZ,+CAA+C;AAC/C,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;CACzB;AAED,8CAA8C;AAC9C,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;CACxB;AAED,4CAA4C;AAC5C,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAC5B,QAAQ,CAAC,IAAI,EAAE,SAAS,OAAO,EAAE,CAAC;IAClC,yEAAyE;IACzE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,2CAA2C;AAC3C,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,0CAA0C;AAC1C,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IACrB,QAAQ,CAAC,KAAK,EAAE,SAAS,IAAI,EAAE,CAAC;CACjC;AAED,0CAA0C;AAC1C,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IACrB,QAAQ,CAAC,KAAK,EAAE,SAAS,IAAI,EAAE,CAAC;CACjC;AAED,kDAAkD;AAClD,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IACrB,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;CACrB"}
package/dist/rule.js ADDED
@@ -0,0 +1,20 @@
1
+ /**
2
+ * The rule language, as data.
3
+ *
4
+ * A rule is a plain JSON value with a `type` tag — not an object with methods.
5
+ * That is what makes storing one, sending one over a wire, and validating one
6
+ * cost nothing: the document *is* the rule. It also means a new operation over
7
+ * rules is a new function rather than a new method on every rule type, which
8
+ * matters because there are many operations coming — evaluating, describing,
9
+ * validating, rendering, diffing — and comparatively few rule types.
10
+ */
11
+ export const WEEKDAYS = [
12
+ "monday",
13
+ "tuesday",
14
+ "wednesday",
15
+ "thursday",
16
+ "friday",
17
+ "saturday",
18
+ "sunday",
19
+ ];
20
+ //# sourceMappingURL=rule.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rule.js","sourceRoot":"","sources":["../src/rule.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,MAAM,CAAC,MAAM,QAAQ,GAAG;IACtB,QAAQ;IACR,SAAS;IACT,WAAW;IACX,UAAU;IACV,QAAQ;IACR,UAAU;IACV,QAAQ;CACA,CAAC"}
@@ -0,0 +1,69 @@
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 { type Cascade } from "./cascade.js";
15
+ import { type Interval } from "./interval.js";
16
+ import { type PlainRule } from "./plain-forms.js";
17
+ /**
18
+ * When something is open, and the questions worth asking about that.
19
+ *
20
+ * A `Schedule` is a `Cascade<boolean>`, so anything that takes a cascade takes
21
+ * one of these. The methods below are the common half said plainly; the
22
+ * cascade underneath is the whole of it.
23
+ */
24
+ export interface Schedule extends Cascade<boolean> {
25
+ /**
26
+ * Open during these times. With hours, inside them on those days; without,
27
+ * for the whole of them.
28
+ */
29
+ readonly open: (scope: PlainRule, hours?: PlainRule) => Schedule;
30
+ /** Closed for the whole of these times, whatever was said before. */
31
+ readonly closed: (scope: PlainRule) => Schedule;
32
+ /**
33
+ * The hours on this day, in place of whatever was said before.
34
+ *
35
+ * Instead of, not as well as: the usual hours do not show through the part
36
+ * this leaves out, which is what "we close early on the eleventh" means and
37
+ * what makes it different from being shut between three and five.
38
+ */
39
+ readonly hoursOn: (day: PlainRule, hours: PlainRule) => Schedule;
40
+ /** Whether it is open at that moment. */
41
+ readonly isOpen: (at: Temporal.ZonedDateTime) => boolean;
42
+ /**
43
+ * The next stretch it is open, at or after a moment, or `undefined` if there
44
+ * is none within `within` of it.
45
+ *
46
+ * `within` bounds how far to look and not what is found: a stretch that
47
+ * starts inside the horizon is returned whole, ending when it really closes
48
+ * rather than where the search stopped. That differs from `next` on a rule,
49
+ * which clips its answer to the window it was given.
50
+ *
51
+ * A schedule that is never open has no answer to give and no way to discover
52
+ * that, so pass `within` when that is a possibility.
53
+ */
54
+ readonly opensNext: (at: Temporal.ZonedDateTime, within?: Temporal.Duration) => Interval | undefined;
55
+ /** How long it is open between two moments. */
56
+ readonly openBetween: (from: Temporal.ZonedDateTime, to: Temporal.ZonedDateTime) => Temporal.Duration;
57
+ }
58
+ /**
59
+ * An empty schedule: open for nothing until something says otherwise.
60
+ *
61
+ * ```ts
62
+ * const openingHours = schedule()
63
+ * .open(weekdays(), "09:00-17:00")
64
+ * .closed("2026-12-25")
65
+ * .hoursOn("2026-03-11", "09:00-15:00");
66
+ * ```
67
+ */
68
+ export declare function schedule(): Schedule;
69
+ //# sourceMappingURL=schedule.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schedule.d.ts","sourceRoot":"","sources":["../src/schedule.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,EAAE,KAAK,OAAO,EAA8B,MAAM,cAAc,CAAC;AAExE,OAAO,EAAY,KAAK,QAAQ,EAAE,MAAM,eAAe,CAAC;AACxD,OAAO,EAAmB,KAAK,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAOnE;;;;;;GAMG;AACH,MAAM,WAAW,QAAS,SAAQ,OAAO,CAAC,OAAO,CAAC;IAChD;;;OAGG;IACH,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,EAAE,SAAS,EAAE,KAAK,CAAC,EAAE,SAAS,KAAK,QAAQ,CAAC;IAEjE,qEAAqE;IACrE,QAAQ,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,QAAQ,CAAC;IAEhD;;;;;;OAMG;IACH,QAAQ,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,KAAK,QAAQ,CAAC;IAEjE,yCAAyC;IACzC,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,QAAQ,CAAC,aAAa,KAAK,OAAO,CAAC;IAEzD;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,SAAS,EAAE,CAClB,EAAE,EAAE,QAAQ,CAAC,aAAa,EAC1B,MAAM,CAAC,EAAE,QAAQ,CAAC,QAAQ,KACvB,QAAQ,GAAG,SAAS,CAAC;IAE1B,+CAA+C;IAC/C,QAAQ,CAAC,WAAW,EAAE,CACpB,IAAI,EAAE,QAAQ,CAAC,aAAa,EAC5B,EAAE,EAAE,QAAQ,CAAC,aAAa,KACvB,QAAQ,CAAC,QAAQ,CAAC;CACxB;AA+DD;;;;;;;;;GASG;AACH,wBAAgB,QAAQ,IAAI,QAAQ,CAEnC"}