slotlock 0.8.0 → 0.10.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.
- package/CHANGELOG.md +542 -0
- package/NOTICE +2 -0
- package/README.md +4 -2
- package/agent-server.d.ts +25 -5
- package/agent-server.js +152 -7
- package/agent-store-backend.d.ts +6 -1
- package/agent-store-backend.js +1068 -68
- package/application-api.d.ts +28 -1
- package/application-api.js +789 -21
- package/approvals.d.ts +2 -0
- package/approvals.js +12 -5
- package/assistant.d.ts +34 -8
- package/assistant.js +81 -26
- package/availability-store.d.ts +193 -0
- package/availability-store.js +566 -0
- package/booking-rules.d.ts +103 -5
- package/booking-rules.js +263 -15
- package/config.d.ts +50 -0
- package/config.js +107 -0
- package/dashboard-assistant-ui.d.ts +1 -1
- package/dashboard-assistant-ui.js +109 -8
- package/dashboard-scope.d.ts +7 -0
- package/dashboard-scope.js +21 -0
- package/dashboard.d.ts +152 -8
- package/dashboard.js +3868 -399
- package/ddl-identity.d.ts +25 -0
- package/ddl-identity.js +881 -0
- package/ddl-scim.d.ts +5 -0
- package/ddl-scim.js +740 -0
- package/ddl-sso.d.ts +15 -0
- package/ddl-sso.js +1033 -0
- package/ddl.d.ts +44 -9
- package/ddl.js +2872 -47
- package/domains.d.ts +117 -0
- package/domains.js +340 -0
- package/event-feed.d.ts +2 -2
- package/event-feed.js +24 -34
- package/holiday-import.d.ts +38 -0
- package/holiday-import.js +56 -0
- package/holidays.d.ts +38 -0
- package/holidays.js +209 -0
- package/hours.d.ts +125 -0
- package/hours.js +224 -0
- package/index.d.ts +24 -5
- package/index.js +17 -3
- package/interop.d.ts +25 -7
- package/interop.js +145 -17
- package/invitation-email.d.ts +26 -0
- package/invitation-email.js +73 -0
- package/lifecycle.d.ts +31 -0
- package/lifecycle.js +115 -0
- package/maintenance.d.ts +26 -2
- package/maintenance.js +70 -2
- package/metrics.js +3 -0
- package/oidc.d.ts +135 -0
- package/oidc.js +452 -0
- package/openapi.json +20172 -9850
- package/package.json +2 -1
- package/ranges.d.ts +28 -0
- package/ranges.js +135 -0
- package/resource-search.d.ts +19 -0
- package/resource-search.js +153 -0
- package/rest-api.js +7 -0
- package/rules.d.ts +2 -0
- package/rules.js +2 -0
- package/scim-server.d.ts +19 -0
- package/scim-server.js +243 -0
- package/scim.d.ts +180 -0
- package/scim.js +416 -0
- package/self-host.d.ts +19 -6
- package/self-host.js +483 -28
- package/slotlock.manifest.json +1 -1
- package/store.d.ts +637 -23
- package/store.js +2529 -147
- package/types.d.ts +230 -0
- package/types.js +68 -1
- package/workspace-domains.d.ts +146 -0
- package/workspace-domains.js +249 -0
- package/workspace-roles.d.ts +47 -0
- package/workspace-roles.js +304 -0
- package/workspace-scim.d.ts +89 -0
- package/workspace-scim.js +157 -0
- package/workspace-sso.d.ts +131 -0
- package/workspace-sso.js +180 -0
- package/workspaces.d.ts +240 -0
- package/workspaces.js +433 -0
package/holidays.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/** The longest holiday calendar Slotlock reads: a megabyte. */
|
|
2
|
+
export declare const SLOTLOCK_MAX_HOLIDAY_CALENDAR_BYTES = 1048576;
|
|
3
|
+
/** Closures one import may make. */
|
|
4
|
+
export declare const SLOTLOCK_MAX_HOLIDAY_DAYS = 1000;
|
|
5
|
+
export interface SlotlockHoliday {
|
|
6
|
+
/** The event's UID, with the day for each occurrence of a recurring one: importing again matches it. */
|
|
7
|
+
uid: string;
|
|
8
|
+
startsOn: string;
|
|
9
|
+
endsOn: string;
|
|
10
|
+
label: string;
|
|
11
|
+
}
|
|
12
|
+
export interface ParsedHolidayCalendar {
|
|
13
|
+
holidays: SlotlockHoliday[];
|
|
14
|
+
/** Events that were not all-day, cancelled, or outside the span. */
|
|
15
|
+
skipped: number;
|
|
16
|
+
/** The calendar's own name (X-WR-CALNAME), when it gives one: at most 200 characters. */
|
|
17
|
+
name?: string;
|
|
18
|
+
}
|
|
19
|
+
export declare function invalidHolidayCalendar(message: string): Error & {
|
|
20
|
+
code: 'invalid_holiday_calendar';
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* The days off an iCalendar text names from `from` to `to` (`YYYY-MM-DD`, inclusive): each all-day
|
|
24
|
+
* VEVENT, from DTSTART to the day before DTEND (one day without DTEND or DURATION), and each
|
|
25
|
+
* occurrence of a recurring one. Refuses (`invalid_holiday_calendar`) text that is not iCalendar,
|
|
26
|
+
* more than SLOTLOCK_MAX_HOLIDAY_DAYS holidays, and one longer than SLOTLOCK_MAX_EXCEPTION_DAYS.
|
|
27
|
+
*/
|
|
28
|
+
export declare function parseHolidayCalendar(text: string, span: {
|
|
29
|
+
from: string;
|
|
30
|
+
to: string;
|
|
31
|
+
}): ParsedHolidayCalendar;
|
|
32
|
+
/**
|
|
33
|
+
* The text at a holiday calendar's URL: `https` only, through `fetch` (give it the guarded fetch, so
|
|
34
|
+
* a private address is refused), at most SLOTLOCK_MAX_HOLIDAY_CALENDAR_BYTES, within ten seconds.
|
|
35
|
+
* Refuses `invalid_holiday_calendar` for anything else, and `holiday_calendar_unreachable` when it
|
|
36
|
+
* cannot be read.
|
|
37
|
+
*/
|
|
38
|
+
export declare function fetchHolidayCalendar(fetcher: typeof fetch, url: string): Promise<string>;
|
package/holidays.js
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
// Holiday calendars (RFC 0004 B1): an iCalendar file of days off (a public-holiday feed, a
|
|
2
|
+
// company's closures) read as closures. Each all-day VEVENT closes the local days it covers;
|
|
3
|
+
// recurring ones are expanded over a bounded span. Timed events are not days off and are skipped.
|
|
4
|
+
import ICAL from 'ical.js';
|
|
5
|
+
import { SLOTLOCK_MAX_EXCEPTION_DAYS, dayToDate, epochDay } from './hours.js';
|
|
6
|
+
/** The longest holiday calendar Slotlock reads: a megabyte. */
|
|
7
|
+
export const SLOTLOCK_MAX_HOLIDAY_CALENDAR_BYTES = 1_048_576;
|
|
8
|
+
/** Closures one import may make. */
|
|
9
|
+
export const SLOTLOCK_MAX_HOLIDAY_DAYS = 1_000;
|
|
10
|
+
/** How long Slotlock waits for a holiday calendar URL. */
|
|
11
|
+
const FETCH_TIMEOUT_MS = 10_000;
|
|
12
|
+
const MAX_LABEL = 200;
|
|
13
|
+
/** Steps through one recurring event's occurrences, and through all of a calendar's. */
|
|
14
|
+
const MAX_STEPS_PER_EVENT = 5_000;
|
|
15
|
+
const MAX_STEPS_PER_CALENDAR = 20_000;
|
|
16
|
+
/**
|
|
17
|
+
* Repeats a calendar of days off has no use for, refused before they are expanded: ical.js steps a
|
|
18
|
+
* DAILY rule day by day until it finds a day of the month or year that may never come (February
|
|
19
|
+
* 30), and never returns; a rule more often than daily cannot name whole days.
|
|
20
|
+
*/
|
|
21
|
+
const SUB_DAILY = new Set(['SECONDLY', 'MINUTELY', 'HOURLY']);
|
|
22
|
+
const DAILY_FILTERS = ['BYMONTHDAY', 'BYYEARDAY', 'BYWEEKNO'];
|
|
23
|
+
/** Why a recurrence rule cannot be read here, or null when it can. */
|
|
24
|
+
function unreadableRule(rule) {
|
|
25
|
+
const recur = rule;
|
|
26
|
+
const freq = String(recur?.freq ?? '').toUpperCase();
|
|
27
|
+
if (SUB_DAILY.has(freq))
|
|
28
|
+
return `it repeats ${freq.toLowerCase()}, and a holiday is a whole day`;
|
|
29
|
+
if (freq === 'DAILY' && DAILY_FILTERS.some((part) => recur?.parts?.[part] !== undefined)) {
|
|
30
|
+
return 'it repeats daily on days of the month or year: repeat it monthly or yearly instead';
|
|
31
|
+
}
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
export function invalidHolidayCalendar(message) {
|
|
35
|
+
return Object.assign(new Error(message), { code: 'invalid_holiday_calendar' });
|
|
36
|
+
}
|
|
37
|
+
const pad = (value, width = 2) => String(value).padStart(width, '0');
|
|
38
|
+
const dateOf = (time) => `${pad(time.year, 4)}-${pad(time.month)}-${pad(time.day)}`;
|
|
39
|
+
/**
|
|
40
|
+
* The days off an iCalendar text names from `from` to `to` (`YYYY-MM-DD`, inclusive): each all-day
|
|
41
|
+
* VEVENT, from DTSTART to the day before DTEND (one day without DTEND or DURATION), and each
|
|
42
|
+
* occurrence of a recurring one. Refuses (`invalid_holiday_calendar`) text that is not iCalendar,
|
|
43
|
+
* more than SLOTLOCK_MAX_HOLIDAY_DAYS holidays, and one longer than SLOTLOCK_MAX_EXCEPTION_DAYS.
|
|
44
|
+
*/
|
|
45
|
+
export function parseHolidayCalendar(text, span) {
|
|
46
|
+
if (Buffer.byteLength(text) > SLOTLOCK_MAX_HOLIDAY_CALENDAR_BYTES) {
|
|
47
|
+
throw invalidHolidayCalendar('The calendar is larger than a megabyte');
|
|
48
|
+
}
|
|
49
|
+
const first = epochDay(span.from);
|
|
50
|
+
const last = epochDay(span.to);
|
|
51
|
+
if (first === null || last === null || last < first)
|
|
52
|
+
throw invalidHolidayCalendar('The span is not a range of days');
|
|
53
|
+
let root;
|
|
54
|
+
try {
|
|
55
|
+
root = new ICAL.Component(ICAL.parse(text));
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
throw invalidHolidayCalendar('The text is not iCalendar');
|
|
59
|
+
}
|
|
60
|
+
if (root.name !== 'vcalendar')
|
|
61
|
+
throw invalidHolidayCalendar('The text is not a VCALENDAR');
|
|
62
|
+
const holidays = [];
|
|
63
|
+
let skipped = 0;
|
|
64
|
+
let steps = 0;
|
|
65
|
+
const vevents = root.getAllSubcomponents('vevent');
|
|
66
|
+
// Each series' overrides, found once: ical.js otherwise looks through every event for each
|
|
67
|
+
// event, so a calendar of many events took minutes.
|
|
68
|
+
const overrides = new Map();
|
|
69
|
+
for (const component of vevents) {
|
|
70
|
+
if (!component.hasProperty('recurrence-id'))
|
|
71
|
+
continue;
|
|
72
|
+
const uid = String(component.getFirstPropertyValue('uid') ?? '');
|
|
73
|
+
overrides.set(uid, [...(overrides.get(uid) ?? []), component]);
|
|
74
|
+
}
|
|
75
|
+
const add = (holiday) => {
|
|
76
|
+
holidays.push(holiday);
|
|
77
|
+
if (holidays.length > SLOTLOCK_MAX_HOLIDAY_DAYS) {
|
|
78
|
+
throw invalidHolidayCalendar(`The calendar names more than ${SLOTLOCK_MAX_HOLIDAY_DAYS} holidays in the span`);
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
for (const component of vevents) {
|
|
82
|
+
// An override of one occurrence is read with its series.
|
|
83
|
+
if (component.hasProperty('recurrence-id'))
|
|
84
|
+
continue;
|
|
85
|
+
const event = new ICAL.Event(component, {
|
|
86
|
+
exceptions: overrides.get(String(component.getFirstPropertyValue('uid') ?? '')) ?? [],
|
|
87
|
+
});
|
|
88
|
+
const start = event.startDate;
|
|
89
|
+
const status = String(component.getFirstPropertyValue('status') ?? '').toUpperCase();
|
|
90
|
+
if (!start || !start.isDate || status === 'CANCELLED') {
|
|
91
|
+
skipped++;
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
const uid = String(component.getFirstPropertyValue('uid') ?? '').slice(0, 300);
|
|
95
|
+
const summary = String(component.getFirstPropertyValue('summary') ?? '').replace(/\s+/g, ' ').trim();
|
|
96
|
+
const label = [...(summary || 'Holiday')].slice(0, MAX_LABEL).join('');
|
|
97
|
+
const end = event.endDate;
|
|
98
|
+
const startDay = epochDay(dateOf(start));
|
|
99
|
+
// DTEND is the day after the last; without one an all-day event is its one day.
|
|
100
|
+
const length = end && end.isDate ? Math.max(1, epochDay(dateOf(end)) - startDay) : 1;
|
|
101
|
+
if (length > SLOTLOCK_MAX_EXCEPTION_DAYS) {
|
|
102
|
+
throw invalidHolidayCalendar(`"${label}" lasts more than ${SLOTLOCK_MAX_EXCEPTION_DAYS} days`);
|
|
103
|
+
}
|
|
104
|
+
const occurrence = (day, key) => {
|
|
105
|
+
const lastDay = day + length - 1;
|
|
106
|
+
if (lastDay < first || day > last) {
|
|
107
|
+
skipped++;
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
add({ uid: key, startsOn: dayToDate(day), endsOn: dayToDate(lastDay), label });
|
|
111
|
+
};
|
|
112
|
+
if (!event.isRecurring()) {
|
|
113
|
+
occurrence(startDay, uid || `${dateOf(start)}:${label}`);
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
for (const rule of component.getAllProperties('rrule')) {
|
|
117
|
+
const why = unreadableRule(rule.getFirstValue());
|
|
118
|
+
if (why)
|
|
119
|
+
throw invalidHolidayCalendar(`"${label}" cannot be read: ${why}`);
|
|
120
|
+
}
|
|
121
|
+
try {
|
|
122
|
+
const iterator = event.iterator();
|
|
123
|
+
for (let next = iterator.next(), own = 0; next && own < MAX_STEPS_PER_EVENT; next = iterator.next(), own++) {
|
|
124
|
+
steps += 1;
|
|
125
|
+
if (steps > MAX_STEPS_PER_CALENDAR) {
|
|
126
|
+
throw invalidHolidayCalendar('The calendar repeats too much to read: give its holidays from a later start');
|
|
127
|
+
}
|
|
128
|
+
const day = epochDay(dateOf(next));
|
|
129
|
+
if (day > last)
|
|
130
|
+
break;
|
|
131
|
+
if (day + length - 1 < first)
|
|
132
|
+
continue;
|
|
133
|
+
occurrence(day, `${uid || label}:${dateOf(next)}`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
if (error.code === 'invalid_holiday_calendar')
|
|
138
|
+
throw error;
|
|
139
|
+
// ical.js refusing a rule it cannot follow.
|
|
140
|
+
throw invalidHolidayCalendar(`"${label}" repeats in a way that cannot be read`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
const given = String(root.getFirstPropertyValue('x-wr-calname') ?? '').replace(/\s+/g, ' ').trim();
|
|
144
|
+
const name = [...given].slice(0, MAX_LABEL).join('');
|
|
145
|
+
return { holidays, skipped, ...(name ? { name } : {}) };
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* The text at a holiday calendar's URL: `https` only, through `fetch` (give it the guarded fetch, so
|
|
149
|
+
* a private address is refused), at most SLOTLOCK_MAX_HOLIDAY_CALENDAR_BYTES, within ten seconds.
|
|
150
|
+
* Refuses `invalid_holiday_calendar` for anything else, and `holiday_calendar_unreachable` when it
|
|
151
|
+
* cannot be read.
|
|
152
|
+
*/
|
|
153
|
+
export async function fetchHolidayCalendar(fetcher, url) {
|
|
154
|
+
let parsed;
|
|
155
|
+
try {
|
|
156
|
+
parsed = new URL(url);
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
throw invalidHolidayCalendar('The calendar URL is not a URL');
|
|
160
|
+
}
|
|
161
|
+
if (parsed.protocol !== 'https:' || parsed.username || parsed.password) {
|
|
162
|
+
throw invalidHolidayCalendar('The calendar URL must be https, without credentials');
|
|
163
|
+
}
|
|
164
|
+
const unreachable = (message) => Object.assign(new Error(message), { code: 'holiday_calendar_unreachable' });
|
|
165
|
+
let response;
|
|
166
|
+
try {
|
|
167
|
+
response = await fetcher(parsed.href, {
|
|
168
|
+
headers: { accept: 'text/calendar' },
|
|
169
|
+
redirect: 'manual',
|
|
170
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
if (error.code === 'private_address') {
|
|
175
|
+
throw invalidHolidayCalendar('The calendar URL names a private address');
|
|
176
|
+
}
|
|
177
|
+
throw unreachable('The calendar URL could not be read');
|
|
178
|
+
}
|
|
179
|
+
if (!response.ok)
|
|
180
|
+
throw unreachable(`The calendar URL answered ${response.status}`);
|
|
181
|
+
const declared = Number(response.headers.get('content-length') ?? '0');
|
|
182
|
+
if (declared > SLOTLOCK_MAX_HOLIDAY_CALENDAR_BYTES)
|
|
183
|
+
throw invalidHolidayCalendar('The calendar is larger than a megabyte');
|
|
184
|
+
const reader = response.body?.getReader();
|
|
185
|
+
if (!reader)
|
|
186
|
+
return '';
|
|
187
|
+
const chunks = [];
|
|
188
|
+
let size = 0;
|
|
189
|
+
for (;;) {
|
|
190
|
+
let read;
|
|
191
|
+
try {
|
|
192
|
+
read = await reader.read();
|
|
193
|
+
}
|
|
194
|
+
catch {
|
|
195
|
+
// Cut off while its body was read (the timeout, or the connection reset).
|
|
196
|
+
throw unreachable('The calendar URL could not be read');
|
|
197
|
+
}
|
|
198
|
+
const { done, value } = read;
|
|
199
|
+
if (done)
|
|
200
|
+
break;
|
|
201
|
+
size += value.byteLength;
|
|
202
|
+
if (size > SLOTLOCK_MAX_HOLIDAY_CALENDAR_BYTES) {
|
|
203
|
+
await reader.cancel();
|
|
204
|
+
throw invalidHolidayCalendar('The calendar is larger than a megabyte');
|
|
205
|
+
}
|
|
206
|
+
chunks.push(value);
|
|
207
|
+
}
|
|
208
|
+
return new TextDecoder().decode(Buffer.concat(chunks));
|
|
209
|
+
}
|
package/hours.d.ts
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import type { Interval, SlotlockInheritedAvailability, WeeklyAvailabilityRule } from './types.js';
|
|
2
|
+
export type { SlotlockInheritedAvailability };
|
|
3
|
+
/** Where a resource's hours come from. */
|
|
4
|
+
export type SlotlockHoursSource = 'resource' | 'location' | 'tenant' | 'server';
|
|
5
|
+
/** One window of an exception's day, in minutes after local midnight. */
|
|
6
|
+
export interface SlotlockExceptionWindow {
|
|
7
|
+
startMinutes: number;
|
|
8
|
+
/** 1 to 1,440; a window may run past midnight into the next day. */
|
|
9
|
+
durationMinutes: number;
|
|
10
|
+
}
|
|
11
|
+
export type SlotlockExceptionOutcome = 'closed' | 'open';
|
|
12
|
+
export type SlotlockExceptionSource = 'manual' | 'ics' | 'vavailability';
|
|
13
|
+
/** Exceptions a tenant may keep, at all its scopes together. */
|
|
14
|
+
export declare const SLOTLOCK_MAX_AVAILABILITY_EXCEPTIONS = 5000;
|
|
15
|
+
/** The longest one exception may last, in days (both ends counted). */
|
|
16
|
+
export declare const SLOTLOCK_MAX_EXCEPTION_DAYS = 367;
|
|
17
|
+
/** Windows one open exception may give each day. */
|
|
18
|
+
export declare const SLOTLOCK_MAX_EXCEPTION_WINDOWS = 4;
|
|
19
|
+
/** A closure or a day of special hours, for the tenant, a location or one resource. */
|
|
20
|
+
export interface SlotlockAvailabilityException {
|
|
21
|
+
id: string;
|
|
22
|
+
tenantRef: string;
|
|
23
|
+
/** The location it applies to (and every resource inside it), or none. */
|
|
24
|
+
location?: string;
|
|
25
|
+
/** The resource it applies to, or none. Neither: the whole tenant. */
|
|
26
|
+
resourceId?: string;
|
|
27
|
+
/** The first local day, `YYYY-MM-DD`. */
|
|
28
|
+
startsOn: string;
|
|
29
|
+
/** The last local day, `YYYY-MM-DD` (inclusive). */
|
|
30
|
+
endsOn: string;
|
|
31
|
+
outcome: SlotlockExceptionOutcome;
|
|
32
|
+
/** For `open`: the hours each of its days has instead of its usual ones. */
|
|
33
|
+
hours?: SlotlockExceptionWindow[];
|
|
34
|
+
label: string;
|
|
35
|
+
source: SlotlockExceptionSource;
|
|
36
|
+
/** For imports: the source's own id, so importing again updates rather than adds. */
|
|
37
|
+
sourceUid?: string;
|
|
38
|
+
/** The holiday calendar it was imported from (listHolidayCalendars), if any. */
|
|
39
|
+
calendarId?: string;
|
|
40
|
+
createdAt: Date;
|
|
41
|
+
}
|
|
42
|
+
/** An exception as the evaluator applies it, ranked from widest scope (0) to the resource's own. */
|
|
43
|
+
export interface SlotlockRankedException {
|
|
44
|
+
startsOn: string;
|
|
45
|
+
endsOn: string;
|
|
46
|
+
outcome: SlotlockExceptionOutcome;
|
|
47
|
+
hours?: readonly SlotlockExceptionWindow[];
|
|
48
|
+
rank: number;
|
|
49
|
+
}
|
|
50
|
+
export declare function invalidException(message: string): Error & {
|
|
51
|
+
code: 'invalid_availability_exception';
|
|
52
|
+
};
|
|
53
|
+
/** Days since 1970-01-01 for a real `YYYY-MM-DD`, or null. */
|
|
54
|
+
export declare function epochDay(date: string): number | null;
|
|
55
|
+
/** `YYYY-MM-DD` for a day since 1970-01-01. */
|
|
56
|
+
export declare function dayToDate(day: number): string;
|
|
57
|
+
/** An exception's windows from untrusted input, or `invalid_availability_exception`. */
|
|
58
|
+
export declare function parseExceptionWindows(value: unknown): SlotlockExceptionWindow[];
|
|
59
|
+
/**
|
|
60
|
+
* An exception's dates and outcome from untrusted input: real dates, the end not before the start,
|
|
61
|
+
* at most SLOTLOCK_MAX_EXCEPTION_DAYS, and hours exactly when it opens.
|
|
62
|
+
*/
|
|
63
|
+
export declare function parseExceptionDays(input: {
|
|
64
|
+
startsOn: unknown;
|
|
65
|
+
endsOn?: unknown;
|
|
66
|
+
outcome: unknown;
|
|
67
|
+
hours?: unknown;
|
|
68
|
+
}): {
|
|
69
|
+
startsOn: string;
|
|
70
|
+
endsOn: string;
|
|
71
|
+
outcome: SlotlockExceptionOutcome;
|
|
72
|
+
hours?: SlotlockExceptionWindow[];
|
|
73
|
+
};
|
|
74
|
+
/** Stored hours that do not read as windows read as none: the day stays closed. */
|
|
75
|
+
export declare function storedExceptionWindows(value: unknown): SlotlockExceptionWindow[];
|
|
76
|
+
/**
|
|
77
|
+
* The open windows of a resource within `window`: its weekly `hours`, with `exceptions` (in rank
|
|
78
|
+
* order) replacing the local days they name in `timezone`. Merged and sorted.
|
|
79
|
+
*/
|
|
80
|
+
export declare function openWindows(params: {
|
|
81
|
+
hours: readonly WeeklyAvailabilityRule[];
|
|
82
|
+
exceptions?: readonly SlotlockRankedException[];
|
|
83
|
+
timezone: string;
|
|
84
|
+
window: Interval;
|
|
85
|
+
}): Interval[];
|
|
86
|
+
/**
|
|
87
|
+
* The weekly hours that apply to a resource and where they come from: its own, else what it
|
|
88
|
+
* inherits, else the server's `fallback`.
|
|
89
|
+
*/
|
|
90
|
+
export declare function applicableHours(resource: {
|
|
91
|
+
availabilityRules?: readonly WeeklyAvailabilityRule[];
|
|
92
|
+
inheritedAvailability?: SlotlockInheritedAvailability;
|
|
93
|
+
}, fallback: readonly WeeklyAvailabilityRule[]): {
|
|
94
|
+
rules: readonly WeeklyAvailabilityRule[];
|
|
95
|
+
source: SlotlockHoursSource;
|
|
96
|
+
location?: string;
|
|
97
|
+
};
|
|
98
|
+
/** Whether `interval` lies wholly inside the open windows. */
|
|
99
|
+
export declare function coveredByOpenWindows(open: readonly Interval[], interval: Interval): boolean;
|
|
100
|
+
/** The first and last UTC day an exception query for `window` must reach (zones span ±14 hours). */
|
|
101
|
+
export declare function exceptionQueryDays(window: Interval): {
|
|
102
|
+
from: string;
|
|
103
|
+
to: string;
|
|
104
|
+
};
|
|
105
|
+
/** One local day as exceptions leave it: no windows is closed. */
|
|
106
|
+
export interface SlotlockExceptionDay {
|
|
107
|
+
/** `YYYY-MM-DD`. */
|
|
108
|
+
date: string;
|
|
109
|
+
windows: SlotlockExceptionWindow[];
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* The days from `from` to `to` (`YYYY-MM-DD`, inclusive) that exceptions replace, each as the
|
|
113
|
+
* nearest exception leaves it, by date.
|
|
114
|
+
*/
|
|
115
|
+
export declare function exceptionDays(exceptions: readonly SlotlockRankedException[], from: string, to: string): SlotlockExceptionDay[];
|
|
116
|
+
/**
|
|
117
|
+
* Consecutive days that read alike as exception ranges: no windows a closure, else hours of their
|
|
118
|
+
* own. The inverse of `exceptionDays` for one scope.
|
|
119
|
+
*/
|
|
120
|
+
export declare function exceptionRanges(days: readonly SlotlockExceptionDay[]): {
|
|
121
|
+
startsOn: string;
|
|
122
|
+
endsOn: string;
|
|
123
|
+
outcome: SlotlockExceptionOutcome;
|
|
124
|
+
hours?: SlotlockExceptionWindow[];
|
|
125
|
+
}[];
|
package/hours.js
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
// Bookable hours with their exceptions (RFC 0004 B1, B2): the one evaluator search and enforced
|
|
2
|
+
// writes share. A resource's hours are its own, else its location's (nearest up the chain), else its
|
|
3
|
+
// tenant's, else the server's. Exceptions then replace whole local days: a closure empties them, an
|
|
4
|
+
// opening gives them hours of their own. Each applies in the resource's time zone, the tenant's
|
|
5
|
+
// first, then each location's from the outermost in, then the resource's own, so the nearer wins.
|
|
6
|
+
import { mergeIntervals } from './engine.js';
|
|
7
|
+
import { expandRules } from './rules.js';
|
|
8
|
+
import { createTimeZoneConverter } from './timezone.js';
|
|
9
|
+
const DAY_MS = 86_400_000;
|
|
10
|
+
const MINUTE_MS = 60_000;
|
|
11
|
+
const DAY_MINUTES = 1_440;
|
|
12
|
+
/** Exceptions a tenant may keep, at all its scopes together. */
|
|
13
|
+
export const SLOTLOCK_MAX_AVAILABILITY_EXCEPTIONS = 5_000;
|
|
14
|
+
/** The longest one exception may last, in days (both ends counted). */
|
|
15
|
+
export const SLOTLOCK_MAX_EXCEPTION_DAYS = 367;
|
|
16
|
+
/** Windows one open exception may give each day. */
|
|
17
|
+
export const SLOTLOCK_MAX_EXCEPTION_WINDOWS = 4;
|
|
18
|
+
export function invalidException(message) {
|
|
19
|
+
return Object.assign(new Error(message), { code: 'invalid_availability_exception' });
|
|
20
|
+
}
|
|
21
|
+
const DATE = /^(\d{4})-(\d{2})-(\d{2})$/;
|
|
22
|
+
/** Days since 1970-01-01 for a real `YYYY-MM-DD`, or null. */
|
|
23
|
+
export function epochDay(date) {
|
|
24
|
+
const match = DATE.exec(date);
|
|
25
|
+
if (!match)
|
|
26
|
+
return null;
|
|
27
|
+
const [, year, month, day] = match.map(Number);
|
|
28
|
+
const ms = Date.UTC(year, month - 1, day);
|
|
29
|
+
const back = new Date(ms);
|
|
30
|
+
if (year < 1 || back.getUTCFullYear() !== year || back.getUTCMonth() !== month - 1 || back.getUTCDate() !== day) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
return Math.round(ms / DAY_MS);
|
|
34
|
+
}
|
|
35
|
+
/** `YYYY-MM-DD` for a day since 1970-01-01. */
|
|
36
|
+
export function dayToDate(day) {
|
|
37
|
+
return new Date(day * DAY_MS).toISOString().slice(0, 10);
|
|
38
|
+
}
|
|
39
|
+
/** An exception's windows from untrusted input, or `invalid_availability_exception`. */
|
|
40
|
+
export function parseExceptionWindows(value) {
|
|
41
|
+
if (!Array.isArray(value) || value.length < 1 || value.length > SLOTLOCK_MAX_EXCEPTION_WINDOWS) {
|
|
42
|
+
throw invalidException(`Open hours must be 1 to ${SLOTLOCK_MAX_EXCEPTION_WINDOWS} windows`);
|
|
43
|
+
}
|
|
44
|
+
return value.map((window, index) => {
|
|
45
|
+
const candidate = window;
|
|
46
|
+
if (typeof candidate !== 'object' ||
|
|
47
|
+
candidate === null ||
|
|
48
|
+
Array.isArray(candidate) ||
|
|
49
|
+
Object.keys(candidate).some((key) => key !== 'startMinutes' && key !== 'durationMinutes') ||
|
|
50
|
+
!Number.isInteger(candidate.startMinutes) ||
|
|
51
|
+
candidate.startMinutes < 0 ||
|
|
52
|
+
candidate.startMinutes >= DAY_MINUTES ||
|
|
53
|
+
!Number.isInteger(candidate.durationMinutes) ||
|
|
54
|
+
candidate.durationMinutes < 1 ||
|
|
55
|
+
candidate.durationMinutes > DAY_MINUTES) {
|
|
56
|
+
throw invalidException(`Window ${index} needs startMinutes (0-1439) and durationMinutes (1-1440)`);
|
|
57
|
+
}
|
|
58
|
+
return { startMinutes: candidate.startMinutes, durationMinutes: candidate.durationMinutes };
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* An exception's dates and outcome from untrusted input: real dates, the end not before the start,
|
|
63
|
+
* at most SLOTLOCK_MAX_EXCEPTION_DAYS, and hours exactly when it opens.
|
|
64
|
+
*/
|
|
65
|
+
export function parseExceptionDays(input) {
|
|
66
|
+
const startsOn = typeof input.startsOn === 'string' ? input.startsOn : '';
|
|
67
|
+
const endsOn = input.endsOn === undefined || input.endsOn === null ? startsOn : String(input.endsOn);
|
|
68
|
+
const first = epochDay(startsOn);
|
|
69
|
+
const last = epochDay(endsOn);
|
|
70
|
+
if (first === null || last === null)
|
|
71
|
+
throw invalidException('Dates must be real days, YYYY-MM-DD');
|
|
72
|
+
if (last < first)
|
|
73
|
+
throw invalidException('The last day must not be before the first');
|
|
74
|
+
if (last - first + 1 > SLOTLOCK_MAX_EXCEPTION_DAYS) {
|
|
75
|
+
throw invalidException(`An exception lasts at most ${SLOTLOCK_MAX_EXCEPTION_DAYS} days`);
|
|
76
|
+
}
|
|
77
|
+
if (input.outcome === 'closed') {
|
|
78
|
+
if (input.hours !== undefined && input.hours !== null)
|
|
79
|
+
throw invalidException('A closure has no hours');
|
|
80
|
+
return { startsOn, endsOn, outcome: 'closed' };
|
|
81
|
+
}
|
|
82
|
+
if (input.outcome === 'open') {
|
|
83
|
+
return { startsOn, endsOn, outcome: 'open', hours: parseExceptionWindows(input.hours) };
|
|
84
|
+
}
|
|
85
|
+
throw invalidException('The outcome must be closed or open');
|
|
86
|
+
}
|
|
87
|
+
/** Stored hours that do not read as windows read as none: the day stays closed. */
|
|
88
|
+
export function storedExceptionWindows(value) {
|
|
89
|
+
try {
|
|
90
|
+
return parseExceptionWindows(value);
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
return [];
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* The open windows of a resource within `window`: its weekly `hours`, with `exceptions` (in rank
|
|
98
|
+
* order) replacing the local days they name in `timezone`. Merged and sorted.
|
|
99
|
+
*/
|
|
100
|
+
export function openWindows(params) {
|
|
101
|
+
const { window, timezone } = params;
|
|
102
|
+
const weekly = expandRules([...params.hours], window, timezone);
|
|
103
|
+
const exceptions = [...(params.exceptions ?? [])].sort((a, b) => a.rank - b.rank);
|
|
104
|
+
if (exceptions.length === 0)
|
|
105
|
+
return weekly;
|
|
106
|
+
const converter = createTimeZoneConverter(timezone);
|
|
107
|
+
const at = (day, minutes) => converter.wallFrameToUtc(day * DAY_MS + minutes * MINUTE_MS, { gap: 'end' }).getTime();
|
|
108
|
+
// The local days the window can touch: a zone is at most fourteen hours from UTC.
|
|
109
|
+
const firstDay = Math.floor(window.start.getTime() / DAY_MS) - 1;
|
|
110
|
+
const lastDay = Math.floor(window.end.getTime() / DAY_MS) + 1;
|
|
111
|
+
let open = weekly;
|
|
112
|
+
for (const exception of exceptions) {
|
|
113
|
+
const from = Math.max(epochDay(exception.startsOn) ?? Number.POSITIVE_INFINITY, firstDay);
|
|
114
|
+
const to = Math.min(epochDay(exception.endsOn) ?? Number.NEGATIVE_INFINITY, lastDay);
|
|
115
|
+
if (from > to)
|
|
116
|
+
continue;
|
|
117
|
+
const removed = { start: at(from, 0), end: at(to + 1, 0) };
|
|
118
|
+
open = open.flatMap((part) => {
|
|
119
|
+
const start = part.start.getTime();
|
|
120
|
+
const end = part.end.getTime();
|
|
121
|
+
if (end <= removed.start || start >= removed.end)
|
|
122
|
+
return [part];
|
|
123
|
+
const kept = [];
|
|
124
|
+
if (start < removed.start)
|
|
125
|
+
kept.push({ start: part.start, end: new Date(removed.start) });
|
|
126
|
+
if (end > removed.end)
|
|
127
|
+
kept.push({ start: new Date(removed.end), end: part.end });
|
|
128
|
+
return kept;
|
|
129
|
+
});
|
|
130
|
+
if (exception.outcome === 'open') {
|
|
131
|
+
for (let day = from; day <= to; day++) {
|
|
132
|
+
for (const hours of exception.hours ?? []) {
|
|
133
|
+
const start = Math.max(at(day, hours.startMinutes), window.start.getTime());
|
|
134
|
+
const end = Math.min(at(day, hours.startMinutes + hours.durationMinutes), window.end.getTime());
|
|
135
|
+
if (end > start)
|
|
136
|
+
open.push({ start: new Date(start), end: new Date(end) });
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return mergeIntervals(open.filter((part) => part.end.getTime() > part.start.getTime()));
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* The weekly hours that apply to a resource and where they come from: its own, else what it
|
|
145
|
+
* inherits, else the server's `fallback`.
|
|
146
|
+
*/
|
|
147
|
+
export function applicableHours(resource, fallback) {
|
|
148
|
+
if (resource.availabilityRules !== undefined)
|
|
149
|
+
return { rules: resource.availabilityRules, source: 'resource' };
|
|
150
|
+
const inherited = resource.inheritedAvailability;
|
|
151
|
+
if (inherited !== undefined) {
|
|
152
|
+
return inherited.location !== undefined
|
|
153
|
+
? { rules: inherited.rules, source: inherited.source, location: inherited.location }
|
|
154
|
+
: { rules: inherited.rules, source: inherited.source };
|
|
155
|
+
}
|
|
156
|
+
return { rules: fallback, source: 'server' };
|
|
157
|
+
}
|
|
158
|
+
/** Whether `interval` lies wholly inside the open windows. */
|
|
159
|
+
export function coveredByOpenWindows(open, interval) {
|
|
160
|
+
let covered = interval.start.getTime();
|
|
161
|
+
for (const part of [...open].sort((a, b) => a.start.getTime() - b.start.getTime())) {
|
|
162
|
+
if (part.start.getTime() > covered)
|
|
163
|
+
break;
|
|
164
|
+
covered = Math.max(covered, part.end.getTime());
|
|
165
|
+
if (covered >= interval.end.getTime())
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
168
|
+
return covered >= interval.end.getTime();
|
|
169
|
+
}
|
|
170
|
+
/** The first and last UTC day an exception query for `window` must reach (zones span ±14 hours). */
|
|
171
|
+
export function exceptionQueryDays(window) {
|
|
172
|
+
return {
|
|
173
|
+
from: dayToDate(Math.floor(window.start.getTime() / DAY_MS) - 1),
|
|
174
|
+
to: dayToDate(Math.floor(window.end.getTime() / DAY_MS) + 1),
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* The days from `from` to `to` (`YYYY-MM-DD`, inclusive) that exceptions replace, each as the
|
|
179
|
+
* nearest exception leaves it, by date.
|
|
180
|
+
*/
|
|
181
|
+
export function exceptionDays(exceptions, from, to) {
|
|
182
|
+
const first = epochDay(from);
|
|
183
|
+
const last = epochDay(to);
|
|
184
|
+
if (first === null || last === null)
|
|
185
|
+
return [];
|
|
186
|
+
const days = new Map();
|
|
187
|
+
for (const exception of [...exceptions].sort((a, b) => a.rank - b.rank)) {
|
|
188
|
+
const start = Math.max(epochDay(exception.startsOn) ?? Number.POSITIVE_INFINITY, first);
|
|
189
|
+
const end = Math.min(epochDay(exception.endsOn) ?? Number.NEGATIVE_INFINITY, last);
|
|
190
|
+
for (let day = start; day <= end; day++) {
|
|
191
|
+
days.set(day, exception.outcome === 'open' ? [...(exception.hours ?? [])] : []);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return [...days.entries()]
|
|
195
|
+
.sort(([a], [b]) => a - b)
|
|
196
|
+
.map(([day, windows]) => ({ date: dayToDate(day), windows }));
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Consecutive days that read alike as exception ranges: no windows a closure, else hours of their
|
|
200
|
+
* own. The inverse of `exceptionDays` for one scope.
|
|
201
|
+
*/
|
|
202
|
+
export function exceptionRanges(days) {
|
|
203
|
+
const sorted = [...days].sort((a, b) => a.date.localeCompare(b.date));
|
|
204
|
+
const ranges = [];
|
|
205
|
+
const key = (windows) => JSON.stringify([...windows].sort((a, b) => a.startMinutes - b.startMinutes || a.durationMinutes - b.durationMinutes));
|
|
206
|
+
let previous = null;
|
|
207
|
+
for (const day of sorted) {
|
|
208
|
+
const at = epochDay(day.date);
|
|
209
|
+
if (at === null)
|
|
210
|
+
continue;
|
|
211
|
+
const same = key(day.windows);
|
|
212
|
+
const last = ranges.at(-1);
|
|
213
|
+
if (last && previous && previous.day === at - 1 && previous.key === same) {
|
|
214
|
+
last.endsOn = day.date;
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
ranges.push(day.windows.length === 0
|
|
218
|
+
? { startsOn: day.date, endsOn: day.date, outcome: 'closed' }
|
|
219
|
+
: { startsOn: day.date, endsOn: day.date, outcome: 'open', hours: JSON.parse(same) });
|
|
220
|
+
}
|
|
221
|
+
previous = { day: at, key: same };
|
|
222
|
+
}
|
|
223
|
+
return ranges;
|
|
224
|
+
}
|
package/index.d.ts
CHANGED
|
@@ -8,7 +8,18 @@ export { type SlotlockMetrics, createSlotlockMetrics } from './metrics.js';
|
|
|
8
8
|
export { expandRules } from './rules.js';
|
|
9
9
|
export { availabilityRuleProblem, parseAvailabilityRules, rulesToWeeklyHours, SLOTLOCK_MAX_AVAILABILITY_RULES, weeklyHoursToRules, } from './weekly-hours.js';
|
|
10
10
|
export type { HoursWindow, Weekday, WeeklyHours } from './weekly-hours.js';
|
|
11
|
-
export { emitVAvailability, parseVAvailability, recurrenceRuleFromRRule, SLOTLOCK_MAX_VAVAILABILITY_CHARACTERS, toJSCalendarEvent, } from './interop.js';
|
|
11
|
+
export { emitVAvailability, parseVAvailability, recurrenceRuleFromRRule, SLOTLOCK_MAX_VAVAILABILITY_CHARACTERS, SLOTLOCK_MAX_VAVAILABILITY_DAYS, toJSCalendarEvent, } from './interop.js';
|
|
12
|
+
export { applicableHours, coveredByOpenWindows, exceptionDays, exceptionRanges, openWindows, parseExceptionDays, SLOTLOCK_MAX_AVAILABILITY_EXCEPTIONS, SLOTLOCK_MAX_EXCEPTION_DAYS, SLOTLOCK_MAX_EXCEPTION_WINDOWS, } from './hours.js';
|
|
13
|
+
export type { SlotlockAvailabilityException, SlotlockExceptionDay, SlotlockExceptionOutcome, SlotlockExceptionSource, SlotlockExceptionWindow, SlotlockHoursSource, SlotlockRankedException, } from './hours.js';
|
|
14
|
+
export type { SlotlockAvailabilityDefaults, SlotlockAvailabilityDefaultsResult, SlotlockAvailabilityExceptionInput, SlotlockOverrideResources, SlotlockHolidayCalendar, SlotlockHolidayImport, } from './availability-store.js';
|
|
15
|
+
export { SLOTLOCK_OVERRIDE_RESOURCES, SLOTLOCK_HOLIDAY_REIMPORT_DAYS, SLOTLOCK_MAX_HOLIDAY_CALENDARS, } from './availability-store.js';
|
|
16
|
+
export { type SlotlockHolidayCalendarRead, type SlotlockHolidaySource, readSlotlockHolidayCalendar, } from './holiday-import.js';
|
|
17
|
+
export { fetchHolidayCalendar, parseHolidayCalendar, SLOTLOCK_MAX_HOLIDAY_CALENDAR_BYTES, SLOTLOCK_MAX_HOLIDAY_DAYS, } from './holidays.js';
|
|
18
|
+
export type { ParsedHolidayCalendar, SlotlockHoliday } from './holidays.js';
|
|
19
|
+
export { SLOTLOCK_BOOKING_ACTIONS, SLOTLOCK_BOOKING_ENDS, SLOTLOCK_BOOKING_STATUSES, SLOTLOCK_BOOKING_TRANSITIONS, slotlockBookingNext, slotlockBookingRefusal, } from './lifecycle.js';
|
|
20
|
+
export type { SlotlockBookingAction, SlotlockBookingStatus, SlotlockEndReason } from './lifecycle.js';
|
|
21
|
+
export { findEarliestRange, sameTimeDaysLater } from './ranges.js';
|
|
22
|
+
export type { SlotlockRangeSearch } from './ranges.js';
|
|
12
23
|
export type { EmitVAvailabilityOptions, JSCalendarAlert, JSCalendarEvent, JSCalendarParticipant, JSCalendarRecurrenceRule, ParsedVAvailability, } from './interop.js';
|
|
13
24
|
export { createTimeZoneConverter, zonedCalendarDate, zonedDateTimeToUtc, zonedWallTime, } from './timezone.js';
|
|
14
25
|
export type { LocalDateTime, TimeZoneConverter, ZonedCalendarDate, ZonedWallTime, } from './timezone.js';
|
|
@@ -25,7 +36,7 @@ export { maintainSlotlockTenant, startSlotlockMaintenance, withSlotlockMaintenan
|
|
|
25
36
|
export type { SlotlockMaintenanceResult, SlotlockMaintenanceScheduler, SlotlockMaintenanceSchedulerOptions, } from './maintenance.js';
|
|
26
37
|
export { createSlotlockApiKeyAuthenticator, createSlotlockApiKeyStore, generateSlotlockApiKey, isSlotlockApiKey, isSlotlockTestTenant, slotlockLiveTenant, SLOTLOCK_TEST_API_KEY_PREFIX, SLOTLOCK_TEST_TENANT_SUFFIX, slotlockApiKeyDigest, SLOTLOCK_API_KEY_ACTIVE_LIMIT, SLOTLOCK_API_KEY_MAX_LIFETIME_DAYS, SLOTLOCK_API_KEY_PREFIX, SLOTLOCK_API_KEY_RETAINED_LIMIT, SLOTLOCK_API_KEY_SCOPES, } from './api-keys.js';
|
|
27
38
|
export { createSlotlockDashboard, createSlotlockDashboardResources, createSlotlockAcceptanceRecords, createSlotlockDashboardState, createSlotlockMemoryDashboardState, SLOTLOCK_DASHBOARD_MAX_RESOURCES, } from './dashboard.js';
|
|
28
|
-
export type { SlotlockDashboard, SlotlockDashboardAssistantOptions, SlotlockDashboardOptions, SlotlockDashboardResources, SlotlockDashboardState, } from './dashboard.js';
|
|
39
|
+
export type { SlotlockDashboard, SlotlockDashboardAssistantOptions, SlotlockDashboardOptions, SlotlockDashboardResources, SlotlockDashboardState, SlotlockInvitationSender, } from './dashboard.js';
|
|
29
40
|
export { createSlotlockAssistantStore, createSlotlockMemoryAssistantStore, SLOTLOCK_ASSISTANT_TOOL_USE, } from './assistant.js';
|
|
30
41
|
export type { SlotlockAssistantStore } from './assistant.js';
|
|
31
42
|
export { createSlotlockPartnerKeyAuthenticator, createSlotlockPartnerStore, parseSlotlockPartnerContract, slotlockPartnerTenantRef, SLOTLOCK_PARTNER_ACCOUNT, SLOTLOCK_PARTNER_ID, SLOTLOCK_PARTNER_SCOPE, } from './partners.js';
|
|
@@ -35,6 +46,13 @@ export type { SlotlockOverageReport } from './overage.js';
|
|
|
35
46
|
export { createSlotlockOtlpExporter, readSlotlockOtlpConfig, slotlockCallSpan } from './otel.js';
|
|
36
47
|
export type { SlotlockOtlpConfig, SlotlockTraceExporter } from './otel.js';
|
|
37
48
|
export type { SlotlockDashboardTeams } from './teams.js';
|
|
49
|
+
export { createSlotlockInvitationEmailSender, SlotlockInvitationEmailError, slotlockInvitationEmail, } from './invitation-email.js';
|
|
50
|
+
export type { SlotlockInvitationEmailConfig } from './invitation-email.js';
|
|
51
|
+
export { createSlotlockDashboardWorkspaces, createSlotlockMemoryDashboardWorkspaces, createSlotlockWorkspaceCheck, } from './workspaces.js';
|
|
52
|
+
export type { SlotlockDashboardWorkspaces, SlotlockGitHubMembership, SlotlockInvitationPreview, SlotlockTeamRefusal, SlotlockTeamWorkspace, SlotlockWorkspaceInvitation, SlotlockWorkspaceMember, SlotlockWorkspaceStanding, SlotlockWorkspaceTeams, SlotlockDashboardSession, SlotlockDashboardSessions, } from './workspaces.js';
|
|
53
|
+
export { SLOTLOCK_DASHBOARD_PAGE_CAPABILITIES, SLOTLOCK_DASHBOARD_POST_CAPABILITIES, SLOTLOCK_FRESH_CAPABILITIES, SLOTLOCK_ROLE_CAPABILITIES, SLOTLOCK_SCOPED_CAPABILITIES, SLOTLOCK_SCOPED_DASHBOARD_PAGES, SLOTLOCK_SCOPED_DASHBOARD_POSTS, SLOTLOCK_WORKSPACE_CAPABILITIES, SLOTLOCK_WORKSPACE_ROLE_NAMES, SLOTLOCK_WORKSPACE_ROLES, isSlotlockWorkspaceRole, slotlockMemberCan, slotlockRoleCan, slotlockRoleKeyScopes, slotlockRoleMayGrant, } from './workspace-roles.js';
|
|
54
|
+
export { slotlockDashboardRequestScope } from './dashboard-scope.js';
|
|
55
|
+
export type { SlotlockWorkspaceCapability, SlotlockWorkspaceRole } from './workspace-roles.js';
|
|
38
56
|
export { closeSlotlockPartnerMonth, priceSlotlockPartnerMonth, slotlockClientLabel, slotlockPartnerMonthChecksum, } from './partner-billing.js';
|
|
39
57
|
export type { SlotlockPartnerAccountMonth, SlotlockPartnerInvoiceLine, SlotlockPartnerMonthClose, } from './partner-billing.js';
|
|
40
58
|
export type { SlotlockPartnerAccount, SlotlockPartnerContract, SlotlockPartnerPlan, SlotlockPartnerPricing, SlotlockPartnerStore, } from './partners.js';
|
|
@@ -43,6 +61,7 @@ export { ICalendarWorkerBusyError, ICalendarWorkerPoolClosedError, createICalend
|
|
|
43
61
|
export type { ICalendarWorkerPool, ICalendarWorkerPoolOptions } from './ical-worker.js';
|
|
44
62
|
export { CalendarContractError, CalendarExpansionLimitError, emitICalendar, emitITipCalendar, expandCalendarEventOccurrences, normalizeCalendarEventContent, normalizeExternalCalendarChange, parseICalendarChanges, parseICalendarFeed, parseTrustedICalendarEvents, recurrenceRuleProblem, storedCalendarEventProblem, } from './sync.js';
|
|
45
63
|
export type { ExternalCalendarChange, ExternalCalendarChangeInput, ExternalCalendarStatus, ICalendarEvent, ICalendarExpansionWindow, ICalendarFeedRead, ICalendarSeriesRefusal, ITipMethod, } from './sync.js';
|
|
46
|
-
export { createSlotlockApplicationRoleGrantsDdl, createSlotlockTenantRlsDdl, SLOTLOCK_API_KEY_FUNCTIONS, SLOTLOCK_API_KEY_TABLES, SLOTLOCK_CORE_DDL, SLOTLOCK_DASHBOARD_FUNCTIONS, SLOTLOCK_DASHBOARD_TABLES, SLOTLOCK_DEFINER_FUNCTIONS, SLOTLOCK_DEFINER_TABLES, SLOTLOCK_OPERATOR_FUNCTIONS, SLOTLOCK_SCHEMA_FUNCTIONS, SLOTLOCK_SCHEMA_TABLES, SLOTLOCK_SCHEMA_VERSION, SLOTLOCK_TENANT_ADMIN_FUNCTIONS, SLOTLOCK_TENANT_ADMIN_TABLES, SLOTLOCK_TENANT_CONTEXT_SETTING, SLOTLOCK_TENANT_RLS_DDL, SLOTLOCK_TENANT_TABLES, } from './ddl.js';
|
|
47
|
-
export { assertSlotlockSchemaCurrent, calendarEventRollingHorizon, createSlotlockStore, readSlotlockSchemaVersion, SLOTLOCK_AGENT_OWNER_COMMAND_QUOTA, SLOTLOCK_AGENT_OWNER_EVENT_QUOTA, SLOTLOCK_CALENDAR_HORIZON_DAYS, SLOTLOCK_EVENT_COMMAND_RETENTION_DAYS, SLOTLOCK_INTERNAL_EVENT_OWNER_REF, SLOTLOCK_LOCAL_RECURRENCE_COVERAGE_SOURCE, SLOTLOCK_MATERIALIZATION_WINDOW_DAYS, SLOTLOCK_MAX_EVENT_DURATION_DAYS, SLOTLOCK_TENANT_WRITE_METHODS, type SlotlockHoldTenantOptions, type SlotlockMigrationOptions, type SlotlockSql, type SlotlockStore, type SlotlockStoreOptions, type SlotlockTenantLimits, type SlotlockTenantStore, } from './store.js';
|
|
48
|
-
export type { AcquireHoldResult, BookingRetentionResult, CalendarAttendee, CalendarAttendeeRole, CalendarCoverageAssessment, CalendarCoverageCommand, CalendarCoverageResult, CalendarEventContent, CalendarEventOccurrence, CalendarEventRetentionResult, CalendarEventStatus, CalendarFreeBusy, CalendarHorizonRollCursor, CalendarHorizonRollResult, CalendarOrganizer, CalendarParticipationStatus, CalendarRecurrence, CalendarRecurrenceException, CalendarReminder, CalendarTransparency, ConfirmHoldResult, CancelCalendarEventResult, CancelExternalReservationResult, CreateReservationResult, DeleteResourceResult, ExternalReservationCommand, ExternalReservationResult, Interval, SlotlockAgendaEntry, SlotlockHold, SlotlockCalendarEvent, SlotlockBookingDescription, SlotlockReservation, SlotlockResource, SlotlockTenantErasure, SlotlockBookingRuleDefaults, PutCalendarEventCommand, PutCalendarEventResult, ReleaseHoldResult, TrustedICalendarEvent, WeeklyAvailabilityRule, } from './types.js';
|
|
64
|
+
export { createSlotlockApplicationRoleGrantsDdl, createSlotlockTenantRlsDdl, SLOTLOCK_API_KEY_FUNCTIONS, SLOTLOCK_API_KEY_TABLES, SLOTLOCK_CORE_DDL, SLOTLOCK_DASHBOARD_FUNCTIONS, SLOTLOCK_DASHBOARD_TABLES, SLOTLOCK_APPEND_ONLY_TABLES, SLOTLOCK_AUDIT_RETENTION_DAYS, SLOTLOCK_CHANGE_RETENTION_BY_PLAN, SLOTLOCK_DEFINER_FUNCTIONS, SLOTLOCK_DEFINER_TABLES, SLOTLOCK_OPERATOR_FUNCTIONS, SLOTLOCK_SCHEMA_FUNCTIONS, SLOTLOCK_SCHEMA_TABLES, SLOTLOCK_SCHEMA_VERSION, SLOTLOCK_TENANT_ADMIN_FUNCTIONS, SLOTLOCK_TENANT_ADMIN_TABLES, SLOTLOCK_TENANT_CONTEXT_SETTING, SLOTLOCK_TENANT_RLS_DDL, SLOTLOCK_TENANT_TABLES, } from './ddl.js';
|
|
65
|
+
export { assertSlotlockSchemaCurrent, calendarEventRollingHorizon, createSlotlockStore, readSlotlockSchemaVersion, SLOTLOCK_AGENT_OWNER_COMMAND_QUOTA, SLOTLOCK_AGENT_OWNER_EVENT_QUOTA, SLOTLOCK_CALENDAR_HORIZON_DAYS, SLOTLOCK_SOURCE_BUSY_MAX, SLOTLOCK_EVENT_COMMAND_RETENTION_DAYS, SLOTLOCK_INTERNAL_EVENT_OWNER_REF, SLOTLOCK_LOCAL_RECURRENCE_COVERAGE_SOURCE, SLOTLOCK_MATERIALIZATION_WINDOW_DAYS, SLOTLOCK_MAX_EVENT_DURATION_DAYS, SLOTLOCK_MAX_CONTACTS_PER_TENANT, SLOTLOCK_PAYMENT_HOLD_MAX_TTL_MS, SLOTLOCK_TENANT_WRITE_METHODS, SLOTLOCK_TENANT_EXPORT_HOURS, SLOTLOCK_TENANT_EXPORT_MAX_BYTES, SLOTLOCK_TENANT_EXPORT_PART_ROWS, SLOTLOCK_MAX_TENANT_EXPORTS, buildSlotlockTenantExport, slotlockAuditEventRecord, type SlotlockAuditEvent, type SlotlockAuditEventInput, type SlotlockSubject, type SlotlockSubjectErasure, type SlotlockSubjectExport, type SlotlockTenantExport, type SlotlockTenantExportPartResult, type SlotlockHoldTenantOptions, type SlotlockMigrationOptions, type SlotlockSql, type SlotlockStore, type SlotlockStoreOptions, type SlotlockTenantLimits, type SlotlockTenantStore, } from './store.js';
|
|
66
|
+
export type { AcquireHoldResult, BookingRetentionResult, CalendarAttendee, CalendarAttendeeRole, CalendarCoverageAssessment, CalendarCoverageCommand, CalendarCoverageResult, CalendarEventContent, CalendarEventOccurrence, CalendarEventRetentionResult, CalendarEventStatus, CalendarFreeBusy, CalendarHorizonRollCursor, CalendarHorizonRollResult, CalendarOrganizer, CalendarParticipationStatus, CalendarRecurrence, CalendarRecurrenceException, CalendarReminder, CalendarTransparency, ConfirmHoldResult, CancelCalendarEventResult, CancelExternalReservationResult, CreateReservationResult, DeleteResourceResult, ExternalReservationCommand, ExternalReservationResult, Interval, SlotlockAgendaEntry, SlotlockHold, MarkHoldPaidResult, SlotlockContact, SlotlockContactConsent, PutContactResult, SourceBusyCommand, SourceBusyResult, SlotlockCalendarEvent, SlotlockBookingDescription, SlotlockReservation, SlotlockResource, SlotlockTenantErasure, SlotlockBookingRuleDefaults, PutCalendarEventCommand, PutCalendarEventResult, ReleaseHoldResult, TrustedICalendarEvent, SlotlockInheritedAvailability, WeeklyAvailabilityRule, SlotlockSubjectTable, SlotlockTenantExportTable, } from './types.js';
|
|
67
|
+
export { SLOTLOCK_SUBJECT_TABLES, SLOTLOCK_TENANT_EXPORT_LEFT_OUT, SLOTLOCK_TENANT_EXPORT_TABLES, } from './types.js';
|