slotlock 0.3.0 → 0.5.1
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 +265 -0
- package/README.md +226 -17
- package/agent-server.d.ts +51 -8
- package/agent-server.js +478 -36
- package/agent-store-backend.d.ts +6 -0
- package/agent-store-backend.js +771 -30
- package/application-api.d.ts +22 -1
- package/application-api.js +412 -3
- package/booking-rules.d.ts +84 -0
- package/booking-rules.js +149 -0
- package/config.d.ts +20 -1
- package/config.js +33 -0
- package/dashboard.d.ts +61 -5
- package/dashboard.js +995 -139
- package/ddl.d.ts +5 -5
- package/ddl.js +591 -5
- package/engine.d.ts +5 -0
- package/engine.js +7 -4
- package/event-feed.d.ts +178 -0
- package/event-feed.js +447 -0
- package/index.d.ts +8 -2
- package/index.js +6 -1
- package/interop.d.ts +117 -0
- package/interop.js +356 -0
- package/maintenance.d.ts +14 -2
- package/maintenance.js +35 -3
- package/mcp-app.d.ts +7 -1
- package/mcp-app.js +259 -40
- package/metrics.d.ts +24 -0
- package/metrics.js +167 -0
- package/openapi.json +8346 -3400
- package/package.json +1 -1
- package/rate-limit.d.ts +10 -1
- package/rate-limit.js +25 -2
- package/rest-api.d.ts +22 -0
- package/rest-api.js +160 -15
- package/self-host.d.ts +26 -3
- package/self-host.js +316 -19
- package/slotlock-manifest.schema.json +1 -0
- package/slotlock.manifest.json +2 -1
- package/store.d.ts +317 -8
- package/store.js +1005 -89
- package/types.d.ts +87 -0
- package/types.js +0 -2
- package/webhooks.d.ts +91 -0
- package/webhooks.js +300 -0
package/store.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { type SlotlockEventFeed } from './event-feed.js';
|
|
2
|
+
import { type SlotlockBookingRules } from './booking-rules.js';
|
|
1
3
|
import type { Sql, TransactionSql } from 'postgres';
|
|
2
4
|
import { SLOTLOCK_CALENDAR_HORIZON_DAYS } from './rules.js';
|
|
3
|
-
import type { AcquireHoldResult, CalendarCoverageCommand, CalendarCoverageResult, CalendarEventRetentionResult, BookingRetentionResult, SlotlockTenantErasure, CalendarFreeBusy, CalendarHorizonRollCursor, CalendarHorizonRollResult, CancelCalendarEventResult, CancelExternalReservationResult, ConfirmHoldResult, CreateReservationResult, DeleteResourceResult, ExternalReservationCommand, ExternalReservationResult, Interval, SlotlockCalendarEvent, SlotlockHold, SlotlockReservation, SlotlockResource, PutCalendarEventCommand, PutCalendarEventResult, ReleaseHoldResult, WeeklyAvailabilityRule } from './types.js';
|
|
5
|
+
import type { AcquireHoldResult, CalendarCoverageCommand, CalendarCoverageResult, CalendarEventContent, CalendarEventRetentionResult, BookingRetentionResult, SlotlockTenantErasure, CalendarFreeBusy, CalendarHorizonRollCursor, CalendarHorizonRollResult, CancelCalendarEventResult, CancelExternalReservationResult, ConfirmHoldResult, CreateReservationResult, DeleteResourceResult, ExternalReservationCommand, ExternalReservationResult, Interval, SlotlockCalendarEvent, SlotlockAgendaEntry, SlotlockHold, SlotlockReservation, SlotlockResource, PutCalendarEventCommand, PutCalendarEventResult, ReleaseHoldResult, WeeklyAvailabilityRule } from './types.js';
|
|
4
6
|
export { SLOTLOCK_CALENDAR_HORIZON_DAYS };
|
|
5
7
|
/**
|
|
6
8
|
* Longest one-off event the store accepts (ten years and some leap days): long enough for any lease
|
|
@@ -16,6 +18,14 @@ export declare const SLOTLOCK_MAX_EVENT_DURATION_DAYS = 3660;
|
|
|
16
18
|
export declare const SLOTLOCK_MATERIALIZATION_WINDOW_DAYS: number;
|
|
17
19
|
/** Trusted provider/maintenance namespace used by backwards-compatible store callers. */
|
|
18
20
|
export declare const SLOTLOCK_INTERNAL_EVENT_OWNER_REF = "internal";
|
|
21
|
+
/** How many live holds one holder (a keyed hold's principal) may have in a tenant at once. */
|
|
22
|
+
export declare const SLOTLOCK_MAX_HOLDS_PER_HOLDER = 20;
|
|
23
|
+
/** Windows one holder may wait for at once (joinWaitlist). */
|
|
24
|
+
export declare const SLOTLOCK_MAX_WAITLIST_PER_HOLDER = 20;
|
|
25
|
+
/** Waiting entries one tenant may have. */
|
|
26
|
+
export declare const SLOTLOCK_MAX_WAITLIST_PER_TENANT = 10000;
|
|
27
|
+
/** How long before its start a booking may be checked in. */
|
|
28
|
+
export declare const SLOTLOCK_CHECK_IN_OPENS_MINUTES = 60;
|
|
19
29
|
/** Default hard ceiling for retained active + tombstoned identities per agent principal. */
|
|
20
30
|
export declare const SLOTLOCK_AGENT_OWNER_EVENT_QUOTA = 1000;
|
|
21
31
|
/** Hard retained command ceiling; includes reserved cancellation capacity for every identity. */
|
|
@@ -60,7 +70,28 @@ export declare function readSlotlockSchemaVersion(sql: SlotlockSql): Promise<num
|
|
|
60
70
|
* migrated it, whose schema this release's code does not know. Both carry `installed` and `expected`.
|
|
61
71
|
*/
|
|
62
72
|
export declare function assertSlotlockSchemaCurrent(sql: SlotlockSql): Promise<void>;
|
|
63
|
-
|
|
73
|
+
/**
|
|
74
|
+
* What makes a put the same request: its identity and the event it asks for. Not the window its
|
|
75
|
+
* occurrences are stored over, which for a series is the rolling horizon from the current UTC
|
|
76
|
+
* midnight: with it in the hash, a retry after midnight was refused as a different request.
|
|
77
|
+
*/
|
|
78
|
+
declare function putCommandHash(params: {
|
|
79
|
+
command: PutCalendarEventCommand;
|
|
80
|
+
ownerRef: string;
|
|
81
|
+
event: CalendarEventContent;
|
|
82
|
+
}): string;
|
|
83
|
+
/**
|
|
84
|
+
* The hash 0.3.0 and earlier stored, with the window in it, so a command stored before the upgrade
|
|
85
|
+
* still replays: always for a one-off event, whose window is its own span, and on the same UTC day
|
|
86
|
+
* for a series. Remove once no stored command can be that old (commands are pruned after 30 days).
|
|
87
|
+
*/
|
|
88
|
+
export declare function legacyPutCommandHash(params: Parameters<typeof putCommandHash>[0] & {
|
|
89
|
+
window: Interval;
|
|
90
|
+
}): string;
|
|
91
|
+
/** Members a pool booking or search considers at most, in id order. */
|
|
92
|
+
export declare const SLOTLOCK_MAX_POOL_MEMBERS = 100;
|
|
93
|
+
export declare function isPoolName(value: unknown): value is string;
|
|
94
|
+
export interface SlotlockStore extends SlotlockEventFeed {
|
|
64
95
|
/**
|
|
65
96
|
* Apply the Slotlock DDL (idempotent). Owns the `slotlock` schema on the connected DB. Refuses
|
|
66
97
|
* (`unsafe_slotlock_schema`, with the `reasons` found) a `slotlock` schema that a role other than the
|
|
@@ -112,6 +143,11 @@ export interface SlotlockStore {
|
|
|
112
143
|
withTenant<T>(tenantRef: string, callback: (store: SlotlockTenantStore) => Promise<T>, options?: {
|
|
113
144
|
isolation?: 'read committed';
|
|
114
145
|
signal?: AbortSignal;
|
|
146
|
+
/**
|
|
147
|
+
* Who is making these changes (an API key's id, an agent's subject), 1-200 characters
|
|
148
|
+
* without control characters: the history (listHistory) records it with each change.
|
|
149
|
+
*/
|
|
150
|
+
actor?: string;
|
|
115
151
|
}): Promise<T>;
|
|
116
152
|
/**
|
|
117
153
|
* INVARIANT (erasure completeness): a tenant integration must use a stable, erasable business
|
|
@@ -181,6 +217,44 @@ export interface SlotlockStore {
|
|
|
181
217
|
id: string;
|
|
182
218
|
rules: readonly WeeklyAvailabilityRule[] | null;
|
|
183
219
|
}): Promise<SlotlockResource | null>;
|
|
220
|
+
/**
|
|
221
|
+
* Set one resource's booking rules (`SlotlockBookingRules`), or clear them (`null`). They apply to
|
|
222
|
+
* bookings made from then on: holds, reservations, moves, and events written with
|
|
223
|
+
* `enforceBookingRules`. Null for an id that is not the tenant's. Refuses rules outside their
|
|
224
|
+
* bounds (`invalid_booking_rules`).
|
|
225
|
+
*/
|
|
226
|
+
setResourceBookingRules(params: {
|
|
227
|
+
tenantRef: string;
|
|
228
|
+
id: string;
|
|
229
|
+
rules: SlotlockBookingRules | null;
|
|
230
|
+
}): Promise<SlotlockResource | null>;
|
|
231
|
+
/**
|
|
232
|
+
* Put one resource in a pool, or take it out (`null`): `pool` is 1-100 characters of a-z, 0-9,
|
|
233
|
+
* `.`, `_` and `-`, starting with a letter or digit (`invalid_pool` otherwise). A resource is in
|
|
234
|
+
* one pool at most. Null for an id that is not the tenant's.
|
|
235
|
+
*/
|
|
236
|
+
setResourcePool(params: {
|
|
237
|
+
tenantRef: string;
|
|
238
|
+
id: string;
|
|
239
|
+
pool: string | null;
|
|
240
|
+
}): Promise<SlotlockResource | null>;
|
|
241
|
+
/**
|
|
242
|
+
* The members of one of the tenant's pools that may be booked, in the order a pool booking tries
|
|
243
|
+
* them (by id): archived ones left out, and with `visibility: 'shared'` private ones too.
|
|
244
|
+
*/
|
|
245
|
+
listPoolMembers(params: {
|
|
246
|
+
tenantRef: string;
|
|
247
|
+
pool: string;
|
|
248
|
+
visibility?: 'shared';
|
|
249
|
+
}): Promise<SlotlockResource[]>;
|
|
250
|
+
/** The tenant's pools by name, with how many members each has that may be booked (its capacity). */
|
|
251
|
+
listPools(params: {
|
|
252
|
+
tenantRef: string;
|
|
253
|
+
visibility?: 'shared';
|
|
254
|
+
}): Promise<{
|
|
255
|
+
pool: string;
|
|
256
|
+
capacity: number;
|
|
257
|
+
}[]>;
|
|
184
258
|
/** Set (or clear, with `null`) the bookable hours of every resource the tenant has; returns how many. */
|
|
185
259
|
setTenantAvailability(params: {
|
|
186
260
|
tenantRef: string;
|
|
@@ -228,7 +302,8 @@ export interface SlotlockStore {
|
|
|
228
302
|
}): Promise<CancelExternalReservationResult>;
|
|
229
303
|
/**
|
|
230
304
|
* Occupy a window WITHOUT committing: the hold blocks rivals under the same EXCLUDE arbiter
|
|
231
|
-
* until `expiresAt` (DB clock —
|
|
305
|
+
* until `expiresAt` (DB clock — the statement's time + ttl, never the app clock), then frees via
|
|
306
|
+
* lazy reap.
|
|
232
307
|
* With `tenantRef`, only a resource of that tenant (`resource_not_found` otherwise, whatever the
|
|
233
308
|
* connection's row-level security).
|
|
234
309
|
*/
|
|
@@ -251,6 +326,19 @@ export interface SlotlockStore {
|
|
|
251
326
|
idempotencyKey?: string;
|
|
252
327
|
/** Whose keys `idempotencyKey` is among, 1-500 UTF-8 bytes (an API key's subject, say). */
|
|
253
328
|
principalRef?: string;
|
|
329
|
+
/**
|
|
330
|
+
* Who the hold belongs to, 1-500 UTF-8 bytes, inside `tenantRef` (required with it): an agent
|
|
331
|
+
* acting for one person, say. `releaseHold` and `putCalendarEvent`'s `fromHold` with a
|
|
332
|
+
* `holderRef` take only their own, and a holder may have `maxHoldsPerHolder` (20) live at once,
|
|
333
|
+
* holding at most `maxHeldHoursPerHolder` together when that is set. Without it the hold has no
|
|
334
|
+
* holder and no cap: an application holding for its customers.
|
|
335
|
+
*/
|
|
336
|
+
holderRef?: string;
|
|
337
|
+
/**
|
|
338
|
+
* Holds taken as alternatives for one booking share a group (a UUID): booking one with
|
|
339
|
+
* `fromHold` releases the rest, and releasing one with a holder releases them all.
|
|
340
|
+
*/
|
|
341
|
+
groupId?: string;
|
|
254
342
|
}): Promise<AcquireHoldResult>;
|
|
255
343
|
/** One of the tenant's live holds, or null (none, expired, confirmed, or another tenant's). */
|
|
256
344
|
getHold(params: {
|
|
@@ -267,6 +355,17 @@ export interface SlotlockStore {
|
|
|
267
355
|
id: string;
|
|
268
356
|
};
|
|
269
357
|
}): Promise<SlotlockHold[]>;
|
|
358
|
+
/**
|
|
359
|
+
* What occupies the tenant's resources in `window` (at most 367 days), every resource at once, by
|
|
360
|
+
* start then id: bookings, calendar events' occurrences and live holds, up to `limit` (1-1000,
|
|
361
|
+
* default 100). An agenda for people to read; listReservations, listCalendarEvents and listHolds
|
|
362
|
+
* page through each.
|
|
363
|
+
*/
|
|
364
|
+
listAgenda(params: {
|
|
365
|
+
tenantRef: string;
|
|
366
|
+
window: Interval;
|
|
367
|
+
limit?: number;
|
|
368
|
+
}): Promise<SlotlockAgendaEntry[]>;
|
|
270
369
|
/**
|
|
271
370
|
* The tenant's booking with this reference as it now is (its current revision), or null: none,
|
|
272
371
|
* cancelled, or a calendar event's occupation, which is not a booking.
|
|
@@ -302,7 +401,13 @@ export interface SlotlockStore {
|
|
|
302
401
|
* Delete a live hold; released=false when it was already gone (expired-reaped or confirmed). With
|
|
303
402
|
* `tenantRef`, only a hold on one of that tenant's resources, as for confirmHold.
|
|
304
403
|
*/
|
|
305
|
-
releaseHold(holdId: string, options?: SlotlockHoldTenantOptions
|
|
404
|
+
releaseHold(holdId: string, options?: SlotlockHoldTenantOptions & {
|
|
405
|
+
/**
|
|
406
|
+
* Only a hold this holder took (`acquireHold`'s `holderRef`), with the rest of its group:
|
|
407
|
+
* released=false for another holder's.
|
|
408
|
+
*/
|
|
409
|
+
holderRef?: string;
|
|
410
|
+
}): Promise<ReleaseHoldResult>;
|
|
306
411
|
/**
|
|
307
412
|
* Reservations overlapping `window` for one resource, as merged-ready half-open intervals. With
|
|
308
413
|
* `tenantRef`, only a resource of that tenant (`resource_not_found` otherwise, whatever the
|
|
@@ -405,7 +510,7 @@ export interface SlotlockStore {
|
|
|
405
510
|
tenantRef: string;
|
|
406
511
|
/** Rows per class per call, 1-1000. Defaults to 100. */
|
|
407
512
|
limit?: number;
|
|
408
|
-
/** Replay window in whole days, 1-3650. Defaults to `
|
|
513
|
+
/** Replay window in whole days, 1-3650. Defaults to the store's `retentionDays`, at least 30. */
|
|
409
514
|
retentionDays?: number;
|
|
410
515
|
}): Promise<CalendarEventRetentionResult>;
|
|
411
516
|
/**
|
|
@@ -417,13 +522,149 @@ export interface SlotlockStore {
|
|
|
417
522
|
* tenant the tenant and its own id as reference, as the migration does. Call until `hasMore` is
|
|
418
523
|
* false.
|
|
419
524
|
*/
|
|
525
|
+
/**
|
|
526
|
+
* Time zone rules that changed since an event was booked (a new tzdata): a future one-off event
|
|
527
|
+
* whose wall-clock time in its zone now falls at another instant. Each is reported once on the
|
|
528
|
+
* feed (`event.timezone_shifted`, with the instant it has and the one its wall time now means).
|
|
529
|
+
* With `move`, it is moved there instead (a new revision, `event.updated` naming `slotlock:tzdata`),
|
|
530
|
+
* unless that overlaps another booking: then it stays and `event.timezone_conflict` reports it,
|
|
531
|
+
* for a person or an agent to decide. Up to `limit` (1-1,000, default 100) events a call.
|
|
532
|
+
*/
|
|
533
|
+
followZoneRules(params: {
|
|
534
|
+
tenantRef: string;
|
|
535
|
+
move?: boolean;
|
|
536
|
+
limit?: number;
|
|
537
|
+
}): Promise<{
|
|
538
|
+
shifted: number;
|
|
539
|
+
moved: number;
|
|
540
|
+
conflicts: number;
|
|
541
|
+
}>;
|
|
542
|
+
/**
|
|
543
|
+
* A source failed to read (a feed answered 500, a token expired): kept as its last error, at most
|
|
544
|
+
* 500 characters, beside its coverage, which stays as it was. `coverage_not_found` for a source
|
|
545
|
+
* that has never recorded coverage on the resource.
|
|
546
|
+
*/
|
|
547
|
+
recordCoverageFailure(params: {
|
|
548
|
+
tenantRef: string;
|
|
549
|
+
resourceId: string;
|
|
550
|
+
source: string;
|
|
551
|
+
error: string;
|
|
552
|
+
}): Promise<{
|
|
553
|
+
ok: true;
|
|
554
|
+
} | {
|
|
555
|
+
ok: false;
|
|
556
|
+
code: 'coverage_not_found' | 'invalid_identity';
|
|
557
|
+
}>;
|
|
558
|
+
/**
|
|
559
|
+
* Retire a source (a sync disconnected or replaced): its coverage of the resource is forgotten, so
|
|
560
|
+
* free/busy and the booking rule `requireCertain` stop waiting on it. False when there was none.
|
|
561
|
+
*/
|
|
562
|
+
deleteCoverage(params: {
|
|
563
|
+
tenantRef: string;
|
|
564
|
+
resourceId: string;
|
|
565
|
+
source: string;
|
|
566
|
+
}): Promise<boolean>;
|
|
567
|
+
/**
|
|
568
|
+
* Each source's coverage of one resource and how healthy it is: when it was last read, its last
|
|
569
|
+
* error, and whether it is `stale` (not read for `maxAgeMs`, default a day, as free/busy judges).
|
|
570
|
+
*/
|
|
571
|
+
listCoverage(params: {
|
|
572
|
+
tenantRef: string;
|
|
573
|
+
resourceId: string;
|
|
574
|
+
maxAgeMs?: number;
|
|
575
|
+
}): Promise<SlotlockCoverageHealth[]>;
|
|
576
|
+
/**
|
|
577
|
+
* Put each source that has gone stale (not read for `maxAgeMs`, default a day) on the feed once
|
|
578
|
+
* (`coverage.stale`), until it is read again. Up to `limit` (default 100); how many.
|
|
579
|
+
*/
|
|
580
|
+
reportStaleCoverage(params: {
|
|
581
|
+
tenantRef: string;
|
|
582
|
+
maxAgeMs?: number;
|
|
583
|
+
limit?: number;
|
|
584
|
+
}): Promise<number>;
|
|
585
|
+
/**
|
|
586
|
+
* Claim a booking by its reference (check-in), so a resource whose booking rules ask for check-in
|
|
587
|
+
* (`checkInMinutes`) does not release it. Check-in opens `SLOTLOCK_CHECK_IN_OPENS_MINUTES` before
|
|
588
|
+
* the start (`check_in_not_open` sooner), and a move to another start clears it. Checking in again
|
|
589
|
+
* keeps the first time. Refused once the booking has ended (`reservation_ended`); a reference with
|
|
590
|
+
* no live booking is `reservation_not_found`. The feed says so (`reservation.checked_in`).
|
|
591
|
+
*/
|
|
592
|
+
checkInReservation(params: {
|
|
593
|
+
tenantRef: string;
|
|
594
|
+
externalRef: string;
|
|
595
|
+
}): Promise<CheckInReservationResult>;
|
|
596
|
+
/**
|
|
597
|
+
* Release up to `limit` (default 100) bookings left unclaimed: on a resource whose booking rules
|
|
598
|
+
* ask for check-in, made since they did, not checked in `checkInMinutes` after the start, and not
|
|
599
|
+
* yet ended. Each is cancelled as `cancelExternalReservation` would (its reference retired at the
|
|
600
|
+
* next revision) and the feed says why (`reservation.no_show`). How many.
|
|
601
|
+
*/
|
|
602
|
+
releaseNoShows(params: {
|
|
603
|
+
tenantRef: string;
|
|
604
|
+
limit?: number;
|
|
605
|
+
}): Promise<number>;
|
|
606
|
+
/**
|
|
607
|
+
* Wait for a window on one resource (`resourceId`) or any member of a pool (`pool`): when it comes
|
|
608
|
+
* free, `offerWaitlist` holds it for `holderRef` for `offerTtlMs` (1 minute to a day, default 15
|
|
609
|
+
* minutes), first come first offered. A holder may wait for `SLOTLOCK_MAX_WAITLIST_PER_HOLDER`
|
|
610
|
+
* windows at once and a tenant `SLOTLOCK_MAX_WAITLIST_PER_TENANT`.
|
|
611
|
+
*/
|
|
612
|
+
joinWaitlist(params: {
|
|
613
|
+
tenantRef: string;
|
|
614
|
+
resourceId?: string;
|
|
615
|
+
pool?: string;
|
|
616
|
+
start: Date;
|
|
617
|
+
end: Date;
|
|
618
|
+
holderRef: string;
|
|
619
|
+
offerTtlMs?: number;
|
|
620
|
+
}): Promise<JoinWaitlistResult>;
|
|
621
|
+
/** The tenant's waitlist entries, oldest first, optionally of one resource, pool or holder. */
|
|
622
|
+
listWaitlist(params: {
|
|
623
|
+
tenantRef: string;
|
|
624
|
+
resourceId?: string;
|
|
625
|
+
pool?: string;
|
|
626
|
+
holderRef?: string;
|
|
627
|
+
limit?: number;
|
|
628
|
+
}): Promise<SlotlockWaitlistEntry[]>;
|
|
629
|
+
/**
|
|
630
|
+
* Stop waiting; with `holderRef`, only that holder's entry. A hold offered and not yet confirmed is
|
|
631
|
+
* released with it. False when there was none.
|
|
632
|
+
*/
|
|
633
|
+
leaveWaitlist(params: {
|
|
634
|
+
tenantRef: string;
|
|
635
|
+
id: string;
|
|
636
|
+
holderRef?: string;
|
|
637
|
+
}): Promise<boolean>;
|
|
638
|
+
/**
|
|
639
|
+
* Offer freed time to the waitlist, in the order entries joined: each waiting entry whose window
|
|
640
|
+
* is free (on its resource, or on the first free member of its pool) gets a hold for its holder
|
|
641
|
+
* and the feed says so (`waitlist.offered`, with the hold). Entries whose window has started are
|
|
642
|
+
* expired (`waitlist.expired`); finished ones older than the change feed's retention are deleted.
|
|
643
|
+
* One tenant's offers run one at a time. One page of `limit` (1-100, default 50) entries: pass
|
|
644
|
+
* `next` back as `after` for the next, until it is null.
|
|
645
|
+
*/
|
|
646
|
+
offerWaitlist(params: {
|
|
647
|
+
tenantRef: string;
|
|
648
|
+
limit?: number;
|
|
649
|
+
after?: {
|
|
650
|
+
createdAt: Date;
|
|
651
|
+
id: string;
|
|
652
|
+
};
|
|
653
|
+
}): Promise<{
|
|
654
|
+
offered: number;
|
|
655
|
+
expired: number;
|
|
656
|
+
next: {
|
|
657
|
+
createdAt: Date;
|
|
658
|
+
id: string;
|
|
659
|
+
} | null;
|
|
660
|
+
}>;
|
|
420
661
|
pruneBookingRetention(params: {
|
|
421
662
|
tenantRef: string;
|
|
422
663
|
/** Rows per class per call, 1-1000. Defaults to 100. */
|
|
423
664
|
limit?: number;
|
|
424
665
|
/**
|
|
425
|
-
* How long a stray retirement is kept, in whole days, 1-3650. Defaults to
|
|
426
|
-
* `
|
|
666
|
+
* How long a stray retirement is kept, in whole days, 1-3650. Defaults to the store's
|
|
667
|
+
* `retentionDays`, at least 30.
|
|
427
668
|
*/
|
|
428
669
|
retentionDays?: number;
|
|
429
670
|
}): Promise<BookingRetentionResult>;
|
|
@@ -494,10 +735,60 @@ export interface SlotlockStore {
|
|
|
494
735
|
coverageMaxAgeMs?: number;
|
|
495
736
|
}): Promise<Interval | null>;
|
|
496
737
|
}
|
|
738
|
+
/** One entry of a tenant's waitlist (joinWaitlist). */
|
|
739
|
+
export interface SlotlockWaitlistEntry {
|
|
740
|
+
id: string;
|
|
741
|
+
/** The resource it waits on, or null when it waits on a pool. */
|
|
742
|
+
resourceId: string | null;
|
|
743
|
+
pool: string | null;
|
|
744
|
+
start: Date;
|
|
745
|
+
end: Date;
|
|
746
|
+
holderRef: string;
|
|
747
|
+
offerTtlMs: number;
|
|
748
|
+
/** `waiting`, `offered` (a hold was made: `holdId` on `offeredResourceId`) or `expired`. */
|
|
749
|
+
status: 'waiting' | 'offered' | 'expired';
|
|
750
|
+
holdId: string | null;
|
|
751
|
+
offeredResourceId: string | null;
|
|
752
|
+
offeredAt: Date | null;
|
|
753
|
+
createdAt: Date;
|
|
754
|
+
}
|
|
755
|
+
export type JoinWaitlistResult = {
|
|
756
|
+
ok: true;
|
|
757
|
+
entry: SlotlockWaitlistEntry;
|
|
758
|
+
} | {
|
|
759
|
+
ok: false;
|
|
760
|
+
code: 'invalid_identity' | 'invalid_window' | 'invalid_ttl' | 'invalid_pool' | 'resource_not_found' | 'resource_archived' | 'pool_not_found' | 'waitlist_limit_reached';
|
|
761
|
+
};
|
|
762
|
+
export type CheckInReservationResult = {
|
|
763
|
+
ok: true;
|
|
764
|
+
reference: string;
|
|
765
|
+
checkedInAt: Date;
|
|
766
|
+
} | {
|
|
767
|
+
ok: false;
|
|
768
|
+
code: 'invalid_identity' | 'reservation_not_found' | 'reservation_ended' | 'check_in_not_open';
|
|
769
|
+
};
|
|
770
|
+
/** One source's coverage of a resource, with its health (listCoverage). */
|
|
771
|
+
export interface SlotlockCoverageHealth {
|
|
772
|
+
source: string;
|
|
773
|
+
start: Date;
|
|
774
|
+
end: Date;
|
|
775
|
+
revision: number;
|
|
776
|
+
/** When it was last read. */
|
|
777
|
+
observedAt: Date;
|
|
778
|
+
lastError: string | null;
|
|
779
|
+
lastErrorAt: Date | null;
|
|
780
|
+
/** Not read for longer than free/busy trusts: time it covers is unproven, not free. */
|
|
781
|
+
stale: boolean;
|
|
782
|
+
}
|
|
497
783
|
export type SlotlockTenantStore = Omit<SlotlockStore, 'applySchema' | 'applyTenantRls' | 'grantApplicationRole' | 'withTenant'>;
|
|
498
784
|
export interface SlotlockStoreOptions {
|
|
499
785
|
/** Namespaced PostgreSQL setting installed by applyTenantRls. Defaults to `slotlock.tenant_ref`. */
|
|
500
786
|
tenantContextSetting?: string;
|
|
787
|
+
/**
|
|
788
|
+
* The bookable hours of a resource without its own, where its booking rules enforce hours
|
|
789
|
+
* (`enforceHours`). None by default: such a resource is then never bookable.
|
|
790
|
+
*/
|
|
791
|
+
defaultAvailabilityRules?: readonly WeeklyAvailabilityRule[];
|
|
501
792
|
/** Hard active + tombstone identity ceiling per non-internal owner. Defaults to 1,000. */
|
|
502
793
|
agentOwnerEventQuota?: number;
|
|
503
794
|
/** Hard retained-command ceiling per non-internal owner. Defaults to 10,000; cannot be disabled. */
|
|
@@ -525,6 +816,24 @@ export interface SlotlockStoreOptions {
|
|
|
525
816
|
* to retry. Not set inside a caller's transaction.
|
|
526
817
|
*/
|
|
527
818
|
tenantLockTimeoutMs?: number;
|
|
819
|
+
/**
|
|
820
|
+
* How many days history is kept, 1-3,650 (default 30): the change feed (and how far back a cursor
|
|
821
|
+
* reaches) and waitlist entries that ended; and, when longer than 30 days, retired idempotency
|
|
822
|
+
* answers, booking references and past events' commands (never shorter, so a delayed retry is still
|
|
823
|
+
* answered as the first try was). Maintenance prunes what is older. A cursor before a change any
|
|
824
|
+
* store has pruned answers `cursor_expired`, so stores set differently never skip a change.
|
|
825
|
+
*/
|
|
826
|
+
retentionDays?: number;
|
|
827
|
+
/**
|
|
828
|
+
* Live holds one holder (`acquireHold`'s `holderRef`) may have at once, 1-10,000. Defaults to
|
|
829
|
+
* SLOTLOCK_MAX_HOLDS_PER_HOLDER (20).
|
|
830
|
+
*/
|
|
831
|
+
maxHoldsPerHolder?: number;
|
|
832
|
+
/**
|
|
833
|
+
* Hours one holder's live holds may hold together, 1-876,000 (none by default): past it a hold is
|
|
834
|
+
* refused with `held_time_limit_reached`, however few holds it has.
|
|
835
|
+
*/
|
|
836
|
+
maxHeldHoursPerHolder?: number;
|
|
528
837
|
}
|
|
529
838
|
/** Per-tenant row limits, each a whole number from 1 to 10,000,000 (resources: to 100,000). */
|
|
530
839
|
export interface SlotlockTenantLimits {
|
|
@@ -565,4 +874,4 @@ export declare function createSlotlockStore(sql: StoreSql, options?: SlotlockSto
|
|
|
565
874
|
* Every store method that writes for a tenant: what a suspended tenant's withTenant store refuses.
|
|
566
875
|
* Maintenance (rolling series, pruning, purging) and erasure are not among them.
|
|
567
876
|
*/
|
|
568
|
-
export declare const SLOTLOCK_TENANT_WRITE_METHODS: readonly ["createResource", "archiveResource", "restoreResource", "deleteResource", "setResourceAvailability", "setTenantAvailability", "createReservation", "createExternalReservation", "rescheduleExternalReservation", "cancelExternalReservation", "acquireHold", "confirmHold", "releaseHold", "putCalendarEvent", "cancelCalendarEvent", "cancelCalendarEventById", "recordCalendarCoverage"];
|
|
877
|
+
export declare const SLOTLOCK_TENANT_WRITE_METHODS: readonly ["createResource", "archiveResource", "restoreResource", "deleteResource", "setResourceAvailability", "setResourceBookingRules", "setResourcePool", "setTenantAvailability", "createReservation", "createExternalReservation", "rescheduleExternalReservation", "cancelExternalReservation", "acquireHold", "confirmHold", "releaseHold", "putCalendarEvent", "cancelCalendarEvent", "cancelCalendarEventById", "recordCalendarCoverage", "createWebhookEndpoint", "deleteWebhookEndpoint", "enableWebhookEndpoint", "recordCoverageFailure", "deleteCoverage", "checkInReservation", "joinWaitlist", "leaveWaitlist"];
|