slotlock 0.6.2 → 0.6.3

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 (62) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +13 -6
  3. package/agent-server.js +1 -5
  4. package/agent-store-backend.d.ts +5 -6
  5. package/agent-store-backend.js +41 -67
  6. package/api-keys.js +1 -1
  7. package/application-api.d.ts +3 -3
  8. package/application-api.js +16 -24
  9. package/assistant.d.ts +18 -27
  10. package/assistant.js +36 -53
  11. package/booking-rules.d.ts +30 -41
  12. package/booking-rules.js +17 -25
  13. package/change-watch.d.ts +1 -0
  14. package/change-watch.js +1 -1
  15. package/cli.js +3 -4
  16. package/config.d.ts +17 -2
  17. package/config.js +44 -6
  18. package/dashboard.js +13 -0
  19. package/ddl.d.ts +20 -27
  20. package/ddl.js +166 -108
  21. package/engine.d.ts +6 -9
  22. package/engine.js +5 -9
  23. package/event-feed.d.ts +23 -31
  24. package/event-feed.js +30 -45
  25. package/event-schema.js +6 -9
  26. package/hooks.d.ts +2 -4
  27. package/hooks.js +2 -4
  28. package/ical-worker-thread.js +3 -4
  29. package/ical-worker.d.ts +8 -10
  30. package/ical-worker.js +7 -9
  31. package/interop.js +1 -4
  32. package/maintenance.d.ts +14 -10
  33. package/maintenance.js +43 -26
  34. package/mcp-app.d.ts +7 -9
  35. package/mcp-app.js +7 -9
  36. package/mcp-modern.d.ts +14 -16
  37. package/mcp-modern.js +23 -27
  38. package/metrics.js +0 -1
  39. package/node-server.d.ts +9 -9
  40. package/node-server.js +25 -34
  41. package/package-version.js +2 -4
  42. package/package.json +1 -1
  43. package/rate-limit.d.ts +6 -1
  44. package/rate-limit.js +24 -8
  45. package/rest-api.d.ts +13 -14
  46. package/rest-api.js +11 -12
  47. package/rules.d.ts +6 -7
  48. package/rules.js +30 -42
  49. package/self-host.d.ts +16 -2
  50. package/self-host.js +42 -21
  51. package/slotlock.manifest.json +1 -1
  52. package/store.d.ts +109 -161
  53. package/store.js +253 -371
  54. package/sync.d.ts +44 -59
  55. package/sync.js +325 -469
  56. package/timezone.d.ts +25 -33
  57. package/timezone.js +37 -52
  58. package/types.d.ts +35 -56
  59. package/webhooks.d.ts +22 -25
  60. package/webhooks.js +35 -40
  61. package/weekly-hours.d.ts +3 -0
  62. package/weekly-hours.js +1 -1
package/timezone.d.ts CHANGED
@@ -8,9 +8,8 @@ export interface LocalDateTime {
8
8
  millisecond?: number;
9
9
  }
10
10
  /**
11
- * Which instant a wall time names where the clocks change. RFC 5545 §3.3.5 reads a time in a fold
12
- * (an hour the clocks repeat) as its first instant, and a time in a gap (an hour they skip) with the
13
- * offset in force before the gap, which moves it forward by the gap: the defaults.
11
+ * Which instant a wall time names where the clocks change. RFC 5545 §3.3.5 defaults: a fold
12
+ * (repeated hour) reads as its first instant, a gap (skipped hour) is moved forward by the gap.
14
13
  */
15
14
  export interface WallTimeChoice {
16
15
  /** In a fold: the earlier instant (default), or the later one. */
@@ -20,45 +19,41 @@ export interface WallTimeChoice {
20
19
  }
21
20
  export interface TimeZoneConverter {
22
21
  /**
23
- * Convert UTC-framed wall-clock fields to the instant that reads as those fields in this zone.
24
- * By default the first instant for a fold, and shifted forward by the gap for a hole.
22
+ * UTC-framed wall-clock fields to the instant that reads as them in this zone (WallTimeChoice
23
+ * defaults).
25
24
  */
26
25
  wallFrameToUtc(wallFrameMs: number, choice?: WallTimeChoice): Date;
27
26
  }
28
27
  /**
29
- * The instant, in ms, that reads as `wallFrameMs` (wall-clock fields framed as UTC) in a zone whose
30
- * offset at an instant `offsetAt` gives, in ms. The offsets in force within fourteen hours either
31
- * side are the candidates (no zone is further than that from UTC, so they include both sides of any
32
- * change the wall time is near); one that maps back to the wall time names it. Two do in a fold,
33
- * none in a gap, where the offset before the gap is the smaller one.
28
+ * The instant (ms) that reads as `wallFrameMs` (wall-clock fields framed as UTC) in a zone with
29
+ * offsets from `offsetAt`. Candidates are the offsets within fourteen hours either side (no zone is
30
+ * further from UTC); one that maps back names it. Two do in a fold, none in a gap.
34
31
  */
35
32
  export declare function wallFrameToInstant(wallFrameMs: number, offsetAt: (utcMs: number) => number, choice?: WallTimeChoice): number;
36
33
  /**
37
- * Date.UTC remaps years 0..99 to 1900..1999. Slotlock accepts ISO calendar years down to 1,
38
- * so construct UTC frames through setUTCFullYear instead of inheriting that legacy quirk.
34
+ * Date.UTC remaps years 0..99 to 1900..1999 and Slotlock accepts years down to 1, so frames are
35
+ * built with setUTCFullYear.
39
36
  */
40
37
  export declare function utcFrame(year: number, month: number, day: number, hour: number, minute: number, second: number, millisecond: number): Date;
41
38
  /** The UTC day an instant falls in, as ZoneOffsets reads a zone's offsets: days since 1970. */
42
39
  export declare function zoneOffsetDay(utcMs: number): number;
43
40
  /**
44
- * How far either side of a wall time converting it to its instant reads the zone's offsets: no zone
45
- * is further than this from UTC. The days from one end to the other hold every offset it reads.
41
+ * How far either side of a wall time its conversion reads offsets: no zone is further than this
42
+ * from UTC.
46
43
  */
47
44
  export declare const ZONE_OFFSET_REACH_MS: number;
48
45
  /**
49
- * How many instants reading `timezone`'s offsets on UTC day `day` asks Intl for with none of them
50
- * kept, beyond the day's two midnights (one each): about twenty to find each change between them.
51
- * A caller that must charge the same for an input however much the process has kept charges each
52
- * midnight and this the first time it reads them.
46
+ * How many Intl reads finding the changes on UTC day `day` takes beyond its two midnights (about
47
+ * twenty per change). Callers that must charge the same whatever the process has cached charge this
48
+ * on first read.
53
49
  */
54
50
  export declare function zoneOffsetChangeReads(timezone: string, day: number): number;
55
51
  export declare function createTimeZoneConverter(timezone: string): TimeZoneConverter;
56
52
  /**
57
- * The spelling Slotlock keeps of an IANA zone name: Intl's, when Intl reads the name as itself in
58
- * another case (`europe/london` is `Europe/London`); otherwise the name as given (`US/Eastern` stays,
59
- * although this Node's ICU may call it `America/New_York`, so a stored name never depends on the
60
- * ICU version that wrote it). A UTC offset (`+05:00`, which Intl accepts as a zone) is not an IANA
61
- * name and is refused, as is anything Intl does not know: both with `code: 'invalid_timezone'`.
53
+ * The spelling Slotlock keeps of an IANA zone: Intl's when it reads the name as itself in another
54
+ * case (`europe/london` is `Europe/London`), else the name as given (`US/Eastern` stays, so a
55
+ * stored name never depends on the ICU version). A UTC offset such as `+05:00` and unknown names
56
+ * are refused with `code: 'invalid_timezone'`.
62
57
  */
63
58
  export declare function canonicalTimeZoneName(timezone: string): string;
64
59
  export interface ZonedCalendarDate {
@@ -68,9 +63,8 @@ export interface ZonedCalendarDate {
68
63
  isoWeekday: number;
69
64
  }
70
65
  /**
71
- * The civil date and ISO weekday an instant falls on in `timezone` — the inverse direction of
72
- * {@link zonedDateTimeToUtc}. Pricing reads "which day is this night" through here so a pickup at
73
- * 00:30 BST on a Friday is a Friday, not the Thursday its UTC timestamp reads as.
66
+ * The civil date and ISO weekday an instant falls on in `timezone` (the inverse of {@link
67
+ * zonedDateTimeToUtc}): a pickup at 00:30 BST on a Friday is a Friday, whatever UTC says.
74
68
  */
75
69
  export declare function zonedCalendarDate(instant: Date, timezone: string): ZonedCalendarDate;
76
70
  export interface ZonedWallTime {
@@ -80,9 +74,8 @@ export interface ZonedWallTime {
80
74
  time: string;
81
75
  }
82
76
  /**
83
- * The civil date and wall-clock time an instant reads as in `timezone`. Agreements and receipts
84
- * state a booking's start and end as a date and a time on the resource's own clock, not the
85
- * server's and not UTC.
77
+ * The civil date and wall-clock time an instant reads as in `timezone`, as agreements and receipts
78
+ * state a booking: on the resource's own clock.
86
79
  */
87
80
  export declare function zonedWallTime(instant: Date, timezone: string): ZonedWallTime;
88
81
  /** How far `timezone` is ahead of UTC at `instant`, in whole minutes (negative west of UTC). */
@@ -98,7 +91,7 @@ export interface ZonedDateTimeFields {
98
91
  }
99
92
  /**
100
93
  * The wall-clock fields an instant reads as in `timezone`, to the second: what a calendar writes
101
- * beside a TZID. Reads through the cached formatters, as every conversion here does.
94
+ * beside a TZID.
102
95
  */
103
96
  export declare function zonedDateTimeFields(instant: Date, timezone: string): ZonedDateTimeFields;
104
97
  export declare function zonedDateTimeToUtc(value: LocalDateTime, timezone: string, choice?: WallTimeChoice): Date;
@@ -111,8 +104,7 @@ export interface ZoneTransition {
111
104
  after: number;
112
105
  }
113
106
  /**
114
- * Every change of `timezone`'s offset from `fromMs` to `toMs`, as Intl reads the zone, from its
115
- * offsets a day at a time (ZoneOffsets): Gaza's clocks go forward on 20 October 2040 and back six
116
- * days later, which a search a week at a time missed.
107
+ * Every change of `timezone`'s offset from `fromMs` to `toMs`, read a day at a time (ZoneOffsets):
108
+ * a weekly search misses short-lived changes such as Gaza's six-day one in October 2040.
117
109
  */
118
110
  export declare function zoneTransitions(timezone: string, fromMs: number, toMs: number): ZoneTransition[];
package/timezone.js CHANGED
@@ -1,10 +1,8 @@
1
1
  const FOURTEEN_HOURS_MS = 14 * 60 * 60 * 1000;
2
2
  /**
3
- * The instant, in ms, that reads as `wallFrameMs` (wall-clock fields framed as UTC) in a zone whose
4
- * offset at an instant `offsetAt` gives, in ms. The offsets in force within fourteen hours either
5
- * side are the candidates (no zone is further than that from UTC, so they include both sides of any
6
- * change the wall time is near); one that maps back to the wall time names it. Two do in a fold,
7
- * none in a gap, where the offset before the gap is the smaller one.
3
+ * The instant (ms) that reads as `wallFrameMs` (wall-clock fields framed as UTC) in a zone with
4
+ * offsets from `offsetAt`. Candidates are the offsets within fourteen hours either side (no zone is
5
+ * further from UTC); one that maps back names it. Two do in a fold, none in a gap.
8
6
  */
9
7
  export function wallFrameToInstant(wallFrameMs, offsetAt, choice = {}) {
10
8
  const offsets = new Set([
@@ -25,8 +23,8 @@ export function wallFrameToInstant(wallFrameMs, offsetAt, choice = {}) {
25
23
  const shifted = wallFrameMs - Math.min(...offsets);
26
24
  if (choice.gap !== 'end')
27
25
  return shifted;
28
- // The gap ends at the change: the first whole second after the instant read with the offset
29
- // after the gap (which is still before it) whose offset differs from that one's.
26
+ // The gap ends at the change: the first whole second after the instant read with the after-gap
27
+ // offset whose offset differs from it.
30
28
  let before = wallFrameMs - Math.max(...offsets);
31
29
  const offsetBefore = offsetAt(before);
32
30
  let after = shifted;
@@ -40,8 +38,8 @@ export function wallFrameToInstant(wallFrameMs, offsetAt, choice = {}) {
40
38
  return after;
41
39
  }
42
40
  /**
43
- * Date.UTC remaps years 0..99 to 1900..1999. Slotlock accepts ISO calendar years down to 1,
44
- * so construct UTC frames through setUTCFullYear instead of inheriting that legacy quirk.
41
+ * Date.UTC remaps years 0..99 to 1900..1999 and Slotlock accepts years down to 1, so frames are
42
+ * built with setUTCFullYear.
45
43
  */
46
44
  export function utcFrame(year, month, day, hour, minute, second, millisecond) {
47
45
  const frame = new Date(0);
@@ -50,10 +48,8 @@ export function utcFrame(year, month, day, hour, minute, second, millisecond) {
50
48
  return frame;
51
49
  }
52
50
  /**
53
- * Formatters already built, by zone name. Building one costs far more than using it, and every
54
- * conversion needs one: a recurring series reads about three times an occurrence. A formatter never
55
- * changes, so one per zone serves every caller. Intl accepts every capitalisation of each zone's
56
- * name, so the map is emptied when it fills rather than growing with whatever names arrive.
51
+ * Formatters by zone name: building one costs far more than using it. Intl accepts any
52
+ * capitalisation of a name, so the map is emptied when full rather than growing.
57
53
  */
58
54
  const formatters = new Map();
59
55
  const MAX_CACHED_FORMATTERS = 256;
@@ -75,21 +71,19 @@ export function zoneOffsetDay(utcMs) {
75
71
  return Math.floor(utcMs / DAY_MS);
76
72
  }
77
73
  /**
78
- * How far either side of a wall time converting it to its instant reads the zone's offsets: no zone
79
- * is further than this from UTC. The days from one end to the other hold every offset it reads.
74
+ * How far either side of a wall time its conversion reads offsets: no zone is further than this
75
+ * from UTC.
80
76
  */
81
77
  export const ZONE_OFFSET_REACH_MS = FOURTEEN_HOURS_MS;
82
78
  /** How many midnights one block of a zone's offsets holds. */
83
79
  const BLOCK_DAYS = 512;
84
80
  const NO_CHANGES = [];
85
81
  /**
86
- * A zone's offsets, read from Intl a UTC day at a time and kept. Intl's formatToParts takes about
87
- * two microseconds and a conversion asks for four to six offsets, so a series that converts a time
88
- * at each of thousands of occurrences spent most of its time there (forty daily London series from
89
- * 2016, 35 seconds). Each UTC midnight's offset is read once; a day whose two midnights agree has
90
- * that offset throughout, and one whose midnights differ has each change in it narrowed to the
91
- * second (scanTransitions). A change and its reversal within one day would be missed; the tests
92
- * compare the zones whose clocks change most with an hourly search.
82
+ * A zone's offsets, read from Intl a UTC day at a time and kept: formatToParts costs about two
83
+ * microseconds and a conversion needs four to six offsets. Each midnight is read once; a day whose
84
+ * midnights agree has that offset throughout, and a day whose midnights differ has each change
85
+ * narrowed to the second (scanTransitions). A change and its reversal within one day would be
86
+ * missed; tests check the most volatile zones against an hourly search.
93
87
  */
94
88
  class ZoneOffsets {
95
89
  /** The offset at each UTC midnight read (NaN where none is), BLOCK_DAYS days to a block. */
@@ -102,7 +96,7 @@ class ZoneOffsets {
102
96
  }
103
97
  offsetAt(utcMs) {
104
98
  const day = zoneOffsetDay(utcMs);
105
- // Not a time value: Intl refuses it, as it always has.
99
+ // Not a time value: let Intl refuse it.
106
100
  if (!Number.isFinite(day))
107
101
  return offsetAt(utcMs, this.formatter);
108
102
  let offset = this.midnight(day);
@@ -170,10 +164,8 @@ function dayStart(day) {
170
164
  return Math.min(Math.max(day * DAY_MS, -MAX_TIME_MS), MAX_TIME_MS);
171
165
  }
172
166
  /**
173
- * Each zone's offsets, by name, as createFormatter keeps formatters. A block of midnights takes 4
174
- * KB; once there are this many (16 MB, about 57 zone-centuries), every zone's are emptied, and so
175
- * are the days with changes once there are this many of those (zones change twice a year at most
176
- * but for a few).
167
+ * Each zone's offsets by name. A block of midnights is 4 KB; past this many blocks (16 MB) every
168
+ * zone's are emptied, and likewise for days with changes.
177
169
  */
178
170
  const zoneOffsetTables = new Map();
179
171
  const MAX_CACHED_BLOCKS = 4_096;
@@ -194,10 +186,9 @@ function zoneOffsets(timezone) {
194
186
  return offsets;
195
187
  }
196
188
  /**
197
- * How many instants reading `timezone`'s offsets on UTC day `day` asks Intl for with none of them
198
- * kept, beyond the day's two midnights (one each): about twenty to find each change between them.
199
- * A caller that must charge the same for an input however much the process has kept charges each
200
- * midnight and this the first time it reads them.
189
+ * How many Intl reads finding the changes on UTC day `day` takes beyond its two midnights (about
190
+ * twenty per change). Callers that must charge the same whatever the process has cached charge this
191
+ * on first read.
201
192
  */
202
193
  export function zoneOffsetChangeReads(timezone, day) {
203
194
  if (timezone === 'UTC')
@@ -222,8 +213,8 @@ function buildFormatter(timezone) {
222
213
  }
223
214
  }
224
215
  function offsetAt(utcMs, formatter) {
225
- // Intl parts expose seconds but not milliseconds. Compute the zone offset against the same
226
- // whole-second instant so sub-second input does not leak into the offset and get rounded away.
216
+ // Intl parts expose seconds, not milliseconds: compute the offset at the whole second so
217
+ // sub-second input is not rounded into it.
227
218
  const wholeSecondMs = Math.floor(utcMs / 1000) * 1000;
228
219
  const values = {};
229
220
  for (const part of formatter.formatToParts(new Date(wholeSecondMs))) {
@@ -246,11 +237,10 @@ export function createTimeZoneConverter(timezone) {
246
237
  };
247
238
  }
248
239
  /**
249
- * The spelling Slotlock keeps of an IANA zone name: Intl's, when Intl reads the name as itself in
250
- * another case (`europe/london` is `Europe/London`); otherwise the name as given (`US/Eastern` stays,
251
- * although this Node's ICU may call it `America/New_York`, so a stored name never depends on the
252
- * ICU version that wrote it). A UTC offset (`+05:00`, which Intl accepts as a zone) is not an IANA
253
- * name and is refused, as is anything Intl does not know: both with `code: 'invalid_timezone'`.
240
+ * The spelling Slotlock keeps of an IANA zone: Intl's when it reads the name as itself in another
241
+ * case (`europe/london` is `Europe/London`), else the name as given (`US/Eastern` stays, so a
242
+ * stored name never depends on the ICU version). A UTC offset such as `+05:00` and unknown names
243
+ * are refused with `code: 'invalid_timezone'`.
254
244
  */
255
245
  export function canonicalTimeZoneName(timezone) {
256
246
  const name = timezone.trim();
@@ -269,9 +259,8 @@ export function canonicalTimeZoneName(timezone) {
269
259
  return resolved.toLowerCase() === name.toLowerCase() ? resolved : name;
270
260
  }
271
261
  /**
272
- * The civil date and ISO weekday an instant falls on in `timezone` — the inverse direction of
273
- * {@link zonedDateTimeToUtc}. Pricing reads "which day is this night" through here so a pickup at
274
- * 00:30 BST on a Friday is a Friday, not the Thursday its UTC timestamp reads as.
262
+ * The civil date and ISO weekday an instant falls on in `timezone` (the inverse of {@link
263
+ * zonedDateTimeToUtc}): a pickup at 00:30 BST on a Friday is a Friday, whatever UTC says.
275
264
  */
276
265
  export function zonedCalendarDate(instant, timezone) {
277
266
  const wall = wallFrame(instant, timezone);
@@ -279,9 +268,8 @@ export function zonedCalendarDate(instant, timezone) {
279
268
  return { date: civilDate(wall), isoWeekday: weekday === 0 ? 7 : weekday };
280
269
  }
281
270
  /**
282
- * The civil date and wall-clock time an instant reads as in `timezone`. Agreements and receipts
283
- * state a booking's start and end as a date and a time on the resource's own clock, not the
284
- * server's and not UTC.
271
+ * The civil date and wall-clock time an instant reads as in `timezone`, as agreements and receipts
272
+ * state a booking: on the resource's own clock.
285
273
  */
286
274
  export function zonedWallTime(instant, timezone) {
287
275
  const wall = wallFrame(instant, timezone);
@@ -295,7 +283,7 @@ export function zonedOffsetMinutes(instant, timezone) {
295
283
  }
296
284
  /**
297
285
  * The wall-clock fields an instant reads as in `timezone`, to the second: what a calendar writes
298
- * beside a TZID. Reads through the cached formatters, as every conversion here does.
286
+ * beside a TZID.
299
287
  */
300
288
  export function zonedDateTimeFields(instant, timezone) {
301
289
  const wall = wallFrame(instant, timezone);
@@ -356,9 +344,8 @@ export function zonedDateTimeToUtc(value, timezone, choice) {
356
344
  return createTimeZoneConverter(timezone).wallFrameToUtc(wallFrame, choice);
357
345
  }
358
346
  /**
359
- * Every change of `timezone`'s offset from `fromMs` to `toMs`, as Intl reads the zone, from its
360
- * offsets a day at a time (ZoneOffsets): Gaza's clocks go forward on 20 October 2040 and back six
361
- * days later, which a search a week at a time missed.
347
+ * Every change of `timezone`'s offset from `fromMs` to `toMs`, read a day at a time (ZoneOffsets):
348
+ * a weekly search misses short-lived changes such as Gaza's six-day one in October 2040.
362
349
  */
363
350
  export function zoneTransitions(timezone, fromMs, toMs) {
364
351
  if (timezone === 'UTC')
@@ -366,9 +353,8 @@ export function zoneTransitions(timezone, fromMs, toMs) {
366
353
  return zoneOffsets(timezone).transitions(Math.floor(fromMs / 1000) * 1000, Math.ceil(toMs / 1000) * 1000);
367
354
  }
368
355
  /**
369
- * The changes of the offset `offsetIn` reads from `fromMs` to `toMs`, found by comparing offsets
370
- * `stepMs` apart and narrowing each change to the second: a change and its reversal both within
371
- * one step are missed.
356
+ * Offset changes found by comparing `offsetIn` `stepMs` apart and narrowing each to the second; a
357
+ * change and its reversal within one step are missed.
372
358
  */
373
359
  function scanTransitions(offsetIn, fromMs, toMs, stepMs) {
374
360
  const found = [];
@@ -380,7 +366,6 @@ function scanTransitions(offsetIn, fromMs, toMs, stepMs) {
380
366
  at = next;
381
367
  continue;
382
368
  }
383
- // The last whole second at `offset` and the first at another.
384
369
  let low = at;
385
370
  let high = next;
386
371
  while (high - low > 1000) {
package/types.d.ts CHANGED
@@ -11,10 +11,9 @@ export interface SlotlockResource {
11
11
  /** Null only for legacy/parity-shadow resources created before authority primitives existed. */
12
12
  tenantRef: string | null;
13
13
  /**
14
- * IANA timezone the resource's availability rules are authored in. expandRules evaluates
15
- * BYDAY/startMinutes as LOCAL wall-clock in this zone (DST-correct, wall-clock ends);
16
- * findNextAvailableFor reads it from the stored resource. Formerly the documented
17
- * Rules remain stable across daylight-saving changes because wall time is resolved in this zone.
14
+ * IANA timezone the availability rules are authored in: expandRules evaluates BYDAY and
15
+ * startMinutes as local wall-clock time in this zone, so rules stay stable across daylight-saving
16
+ * changes.
18
17
  */
19
18
  timezone: string;
20
19
  /**
@@ -23,9 +22,8 @@ export interface SlotlockResource {
23
22
  */
24
23
  availabilityRules?: WeeklyAvailabilityRule[];
25
24
  /**
26
- * `shared` resources are listed to agents and booked through them; `private` ones are invisible
27
- * to agents and reached only by the tenant's own code. Fixed when the resource is created. The
28
- * store always says which; a resource built elsewhere without it is shared.
25
+ * `shared` resources are listed to and booked by agents; `private` ones are invisible to agents.
26
+ * Fixed at creation; absent means shared.
29
27
  */
30
28
  visibility?: 'shared' | 'private';
31
29
  /** When the tenant archived it: no longer listed or booked, its history kept. Absent in use. */
@@ -33,14 +31,13 @@ export interface SlotlockResource {
33
31
  /** Its own booking rules (`SlotlockBookingRules`). Absent when it has none. */
34
32
  bookingRules?: SlotlockBookingRules;
35
33
  /**
36
- * What a booking on it must satisfy: the tenant's default rules, overlaid by its pool's, overlaid
37
- * by its own (`setBookingRuleDefaults`), a field set at a narrower level winning. Absent when no
38
- * rule applies.
34
+ * What a booking on it must satisfy: tenant defaults, overlaid by its pool's, overlaid by its own
35
+ * (`setBookingRuleDefaults`). Absent when no rule applies.
39
36
  */
40
37
  effectiveBookingRules?: SlotlockBookingRules;
41
38
  /**
42
- * The pool it belongs to: interchangeable resources sharing a name, such as a vehicle class or a
43
- * room type, booked by the class (`slotlock_book_pool`). Absent when it is in none.
39
+ * Its pool: interchangeable resources (a vehicle class, a room type) booked by the class
40
+ * (`slotlock_book_pool`).
44
41
  */
45
42
  pool?: string;
46
43
  }
@@ -48,10 +45,7 @@ export interface SlotlockResource {
48
45
  export interface SlotlockReservation {
49
46
  id: string;
50
47
  resourceId: string;
51
- /**
52
- * Null only for live holds, rows on a resource that has no tenant (legacy), and parity-shadow rows.
53
- * A booking made by confirming a hold, or by createReservation, on a tenant's resource has one.
54
- */
48
+ /** Null only for live holds, rows on a tenantless (legacy) resource, and parity-shadow rows. */
55
49
  tenantRef: string | null;
56
50
  /**
57
51
  * Stable booking identity inside tenantRef; null exactly when tenantRef is. A confirmed hold, and
@@ -170,17 +164,15 @@ export type CancelExternalReservationResult = {
170
164
  limit: number;
171
165
  };
172
166
  /**
173
- * VAVAILABILITY-style bookable window (RFC 7953 semantics, weekly subset): a weekly-cadence RRULE
174
- * picks the days; the window on each matching day runs `[startMinutes, startMinutes+durationMinutes)`
175
- * after that day's local midnight in the evaluation timezone (`expandRules`' `timezone`, the
176
- * resource's stored zone for `findNextAvailableFor`, UTC when none is given).
167
+ * VAVAILABILITY-style bookable window (RFC 7953, weekly subset): a weekly RRULE picks the days, and
168
+ * each day's window is `[startMinutes, startMinutes+durationMinutes)` after local midnight in the
169
+ * evaluation timezone (UTC when none is given).
177
170
  */
178
171
  export interface WeeklyAvailabilityRule {
179
172
  /** RRULE content line without the `RRULE:` prefix, e.g. `FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR`. */
180
173
  rrule: string;
181
- /** Window start, minutes after midnight in the evaluation timezone (UTC when none is given) of each matching day (0..1439). */
174
+ /** Window start, minutes after local midnight (0..1439). */
182
175
  startMinutes: number;
183
- /** Window length in minutes (> 0). */
184
176
  durationMinutes: number;
185
177
  }
186
178
  export type CreateReservationResult = {
@@ -243,7 +235,6 @@ export interface SlotlockAgendaEntry {
243
235
  resourceId: string;
244
236
  start: Date;
245
237
  end: Date;
246
- /** A booking, a calendar event's occurrence, or a live hold. */
247
238
  kind: 'booking' | 'event' | 'hold';
248
239
  /** The booking's reference; null for an event's occurrence and a hold. */
249
240
  reference: string | null;
@@ -447,18 +438,17 @@ export interface PutCalendarEventCommand extends CalendarEventContent {
447
438
  materializationWindow?: Interval;
448
439
  source?: string;
449
440
  /**
450
- * Judge the event as a booking made now against the resource's booking rules (its first
451
- * occurrence for notice and advance, every occurrence for step and hours), and keep the
452
- * resource's turnaround after each occurrence. Off by default: an imported event is what happened
453
- * elsewhere. The agent tools set it.
441
+ * Judge the event as a booking made now against the resource's booking rules (first occurrence
442
+ * for notice and advance, every occurrence for step and hours), and keep the resource's
443
+ * turnaround. Off by default, since an imported event already happened elsewhere; the agent tools
444
+ * set it.
454
445
  */
455
446
  enforceBookingRules?: boolean;
456
447
  /**
457
- * Book the time a live hold keeps: the hold must be `holderRef`'s (`acquireHold`'s
458
- * `principalRef`), on this resource, and cover the event, a single occurrence. In one transaction
459
- * the hold and the rest of its group are released and the event takes the time, so no rival can
460
- * come between. Its notice and advance were judged when it was held; step and hours are judged
461
- * again.
448
+ * Book the time a live hold keeps: the hold must be `holderRef`'s, on this resource, and cover
449
+ * the event (a single occurrence). The hold's group is released and the event takes the time in
450
+ * one transaction, so no rival can come between. Notice and advance were judged at hold time;
451
+ * step and hours are judged again.
462
452
  */
463
453
  fromHold?: {
464
454
  holdId: string;
@@ -581,14 +571,11 @@ export type CancelCalendarEventResult = {
581
571
  code: 'revision_conflict';
582
572
  currentRevision: number;
583
573
  };
584
- /**
585
- * A place in the roll's order of due masters: the last one a batch examined. The roll takes the
586
- * masters it has never put back first, then those it has, longest waiting first.
587
- */
574
+ /** A place in the roll's order of due masters: the last one a batch examined. */
588
575
  export interface CalendarHorizonRollCursor {
589
576
  /**
590
- * When the master was due to be examined again, if the roll had put it back; null (or, from a
591
- * cursor of an earlier release, missing) when it had not.
577
+ * When the master was due again, if the roll had deferred it; null (or missing, from an older
578
+ * cursor) when not.
592
579
  */
593
580
  rollRetryAt?: Date | null;
594
581
  materializedEndsAt: Date;
@@ -596,34 +583,27 @@ export interface CalendarHorizonRollCursor {
596
583
  }
597
584
  /** Content-minimized outcome of one bounded recurring-event horizon maintenance batch. */
598
585
  export interface CalendarHorizonRollResult {
599
- /** Due recurring masters examined in deterministic order. */
600
586
  examined: number;
601
587
  /**
602
- * Masters atomically rematerialized from the requested window's start through the later of their
603
- * stored end and the window's: a roll never moves a series' end back.
588
+ * Masters rematerialized through the later of their stored end and the window's: a roll never
589
+ * moves a series' end back.
604
590
  */
605
591
  extended: number;
606
- /**
607
- * Masters whose new occurrence collided; their previous coverage remains intact, and they wait an
608
- * hour before a roll examines them again, behind every master it has not put back.
609
- */
592
+ /** Masters whose new occurrence collided: their coverage stays, and they are deferred an hour. */
610
593
  conflicts: number;
611
594
  /** The ids of those masters: what blocks each is a booking or hold on its new occurrences. */
612
595
  conflictEventIds: string[];
613
596
  /**
614
- * Masters whose rule takes more than the expansion budget to reach the window (one that never
615
- * occurs again, or almost never), or whose roll failed for any other reason; their previous
616
- * coverage remains intact, and they wait an hour
617
- * before a roll examines them again, behind every master it has not put back.
597
+ * Masters over the expansion budget, or whose roll failed otherwise: their coverage stays, and
598
+ * they are deferred an hour.
618
599
  */
619
600
  refused: number;
620
- /** The ids of those masters, to find and fix them. */
621
601
  refusedEventIds: string[];
622
602
  /** True when more due masters remain beyond this bounded batch. */
623
603
  hasMore: boolean;
624
604
  /**
625
- * Pass as `after` for the next batch of this pass, so the masters this batch examined are not
626
- * examined again by every later batch. `null` when none remain.
605
+ * Pass as `after` for the next batch, so this batch's masters are not examined again. `null` when
606
+ * none remain.
627
607
  */
628
608
  next: CalendarHorizonRollCursor | null;
629
609
  }
@@ -635,7 +615,6 @@ export interface CalendarEventRetentionResult {
635
615
  /** True when either class filled its batch, so more rows may already be eligible. */
636
616
  hasMore: boolean;
637
617
  }
638
- /** What erasing a tenant deleted. */
639
618
  /** A tenant's default booking rules (`SlotlockStore.getBookingRuleDefaults`). */
640
619
  export interface SlotlockBookingRuleDefaults {
641
620
  /** The tenant's own defaults, under every pool's; null when it has none. */
@@ -650,13 +629,13 @@ export interface SlotlockTenantErasure {
650
629
  total: number;
651
630
  }
652
631
  export interface BookingRetentionResult {
653
- /** Holds past their expiry: they occupy nothing, and only a rival's write would reap them. */
632
+ /** Holds past their expiry: they occupy nothing, but only a rival's write would reap them. */
654
633
  expiredHoldsDeleted: number;
655
634
  /** Keyed hold answers whose hold has expired: a retry with that key makes a new hold. */
656
635
  holdCommandsDeleted: number;
657
636
  /**
658
- * References a cancel retired with no booking, older than the window: a create with one makes a
659
- * new booking. A cancelled booking's reference stays retired.
637
+ * References a cancel retired with no booking, older than the window. A cancelled booking's
638
+ * reference stays retired.
660
639
  */
661
640
  tombstonesDeleted: number;
662
641
  /** True when any class filled its batch, so more rows may already be eligible. */
package/webhooks.d.ts CHANGED
@@ -17,13 +17,13 @@ export interface SlotlockCloudEvent {
17
17
  }
18
18
  /**
19
19
  * A change as CloudEvents 1.0 (structured mode): `type` is `io.pylota.slotlock.<change type>`, with
20
- * the extension attributes `slotlockorigin` and `slotlockkeyid` when the change records them. A
21
- * `coverage.*` change is redacted as the feed serves it (slotlockServedChange), whoever built it.
20
+ * `slotlockorigin` and `slotlockkeyid` when recorded. A `coverage.*` change is redacted as the feed
21
+ * serves it (slotlockServedChange).
22
22
  */
23
23
  export declare function slotlockCloudEvent(change: SlotlockChange): SlotlockCloudEvent;
24
24
  /**
25
- * The Standard Webhooks signature of one attempt: `v1,` and the base64 HMAC-SHA256, keyed by the
26
- * secret's bytes, of `id.timestamp.body`.
25
+ * The Standard Webhooks signature: `v1,` and the base64 HMAC-SHA256 of `id.timestamp.body`, keyed
26
+ * by the secret's bytes.
27
27
  */
28
28
  export declare function signSlotlockWebhook(params: {
29
29
  secret: string;
@@ -32,9 +32,8 @@ export declare function signSlotlockWebhook(params: {
32
32
  body: string;
33
33
  }): string;
34
34
  /**
35
- * When to try a failed endpoint again, by how many attempts have failed in a row: the Standard
36
- * Webhooks example schedule (5 s, 5 min, 30 min, 2 h, 5 h, then 10 h), with ±10% jitter, and never
37
- * sooner than the endpoint's Retry-After (capped at a day).
35
+ * Retry delay by consecutive failures: the Standard Webhooks example schedule (5 s, 5 min, 30 min,
36
+ * 2 h, 5 h, then 10 h) with ±10% jitter, never sooner than Retry-After (capped at a day).
38
37
  */
39
38
  export declare function slotlockWebhookRetryDelayMs(attempts: number, retryAfterMs?: number, random?: () => number): number;
40
39
  /** Whether `address` is one a webhook may not reach: loopback, private, link-local, and the like. */
@@ -50,19 +49,18 @@ export type SlotlockWebhookSender = (request: {
50
49
  body: string;
51
50
  }) => Promise<SlotlockWebhookResponse>;
52
51
  /**
53
- * POST with Node's http client: no redirect is followed, the answer's body is never read beyond
54
- * 64 KiB, and every address the host resolves to is checked when the socket connects, so a name
55
- * that resolves to a private network (or is rebound to one) is refused unless `allowPrivateNetworks`.
52
+ * POST with Node's http client: no redirect is followed, at most 64 KiB of body is read, and every
53
+ * resolved address is checked at connect time, so a name resolving (or rebound) to a private
54
+ * network is refused unless `allowPrivateNetworks`.
56
55
  */
57
56
  export declare function createSlotlockWebhookSender(options?: {
58
57
  timeoutMs?: number;
59
58
  allowPrivateNetworks?: boolean;
60
59
  }): SlotlockWebhookSender;
61
60
  /**
62
- * A `dns.lookup` that answers every address a name resolves to, and refuses (an error with `code`)
63
- * a name with any private one unless `allowPrivateNetworks`. Given to a socket's connect, it checks
64
- * the addresses the connection will use, so a name rebound to a private address after an earlier
65
- * check is still refused. `resolve` stands in for `dns.lookup` (tests).
61
+ * A `dns.lookup` that returns every address and refuses (with `code`) a name with any private one
62
+ * unless `allowPrivateNetworks`. Used at socket connect, so a rebound name is still refused.
63
+ * `resolve` stands in for `dns.lookup` in tests.
66
64
  */
67
65
  export declare function slotlockPrivateAddressLookup(options: {
68
66
  allowPrivateNetworks: boolean;
@@ -71,11 +69,10 @@ export declare function slotlockPrivateAddressLookup(options: {
71
69
  resolve?: typeof dnsLookup;
72
70
  }): typeof dnsLookup;
73
71
  /**
74
- * A `fetch` for a server reaching a URL a tenant chose (the dashboard assistant's model): Node's
75
- * http client with the webhooks' address check. A URL naming a private address, or a host that
76
- * resolves to one when the socket connects, is refused with an error whose `code` is
77
- * `private_address`, unless `allowPrivateNetworks`. No redirect is followed (the answer is
78
- * returned as is) and no proxy from the environment is used. Takes a string body only.
72
+ * A `fetch` for URLs a tenant chose (the assistant's model), with the webhooks' address check: a
73
+ * private address or a host resolving to one at connect time is refused with `code`
74
+ * `private_address` unless `allowPrivateNetworks`. No redirects, no environment proxy, string
75
+ * bodies only.
79
76
  */
80
77
  export declare function createSlotlockGuardedFetch(options?: {
81
78
  allowPrivateNetworks?: boolean;
@@ -88,9 +85,9 @@ export interface SlotlockWebhookPassResult {
88
85
  failed: number;
89
86
  }
90
87
  /**
91
- * One delivery pass for a tenant: claim each endpoint with changes due, send them in order until
92
- * one fails, and record how far each got. A 2xx is delivered; 410 disables the endpoint; anything
93
- * else, or no answer, is retried on the schedule. The send happens outside any transaction.
88
+ * One delivery pass for a tenant: claim each endpoint with changes due, send in order until one
89
+ * fails, and record progress. 2xx is delivered; 410 disables the endpoint; anything else is retried
90
+ * on the schedule. Sending happens outside any transaction.
94
91
  */
95
92
  export declare function deliverSlotlockWebhooks(params: {
96
93
  feed: Pick<SlotlockEventFeed, 'claimWebhookDeliveries' | 'settleWebhookDelivery'>;
@@ -108,9 +105,9 @@ export interface SlotlockWebhookScheduler {
108
105
  close(): Promise<void>;
109
106
  }
110
107
  /**
111
- * Delivery passes every `intervalMs` (250-60,000, default 2,000) for the tenants `tenants()` lists
112
- * (the store's webhook_tenants(), say) and those touched, one tenant at a time. A pass that fails
113
- * is reported and the next one runs as usual.
108
+ * Delivery passes every `intervalMs` (250-60,000, default 2,000) for `tenants()` (the store's
109
+ * webhook_tenants(), say) and touched tenants. A failed pass is reported and the next runs as
110
+ * usual.
114
111
  */
115
112
  export declare function startSlotlockWebhookDelivery(options: {
116
113
  tenants: () => Promise<readonly string[]>;