slotlock 0.6.2 → 0.7.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 +492 -0
- package/CONTRIBUTING.md +12 -5
- package/README.md +50 -1544
- package/SECURITY.md +5 -2
- package/SPEC.md +1 -1
- package/agent-server.d.ts +160 -8
- package/agent-server.js +560 -49
- package/agent-store-backend.d.ts +39 -7
- package/agent-store-backend.js +1439 -104
- package/api-keys.d.ts +69 -3
- package/api-keys.js +218 -29
- package/application-api.d.ts +59 -6
- package/application-api.js +842 -39
- package/approvals.d.ts +88 -0
- package/approvals.js +293 -0
- package/assistant.d.ts +58 -27
- package/assistant.js +151 -53
- package/billing.d.ts +114 -0
- package/billing.js +258 -0
- package/booking-rules.d.ts +30 -41
- package/booking-rules.js +17 -25
- package/change-watch.d.ts +1 -0
- package/change-watch.js +1 -1
- package/cli.js +3 -4
- package/config.d.ts +42 -6
- package/config.js +114 -7
- package/dashboard-icon.d.ts +1 -0
- package/dashboard-icon.js +5 -0
- package/dashboard.d.ts +203 -7
- package/dashboard.js +1558 -64
- package/ddl.d.ts +67 -35
- package/ddl.js +2732 -162
- package/engine.d.ts +6 -9
- package/engine.js +5 -9
- package/event-feed.d.ts +53 -35
- package/event-feed.js +201 -64
- package/event-schema.js +6 -9
- package/hooks.d.ts +2 -4
- package/hooks.js +2 -4
- package/ical-worker-thread.js +3 -4
- package/ical-worker.d.ts +8 -10
- package/ical-worker.js +7 -9
- package/index.d.ts +15 -4
- package/index.js +8 -2
- package/interop.js +1 -4
- package/maintenance.d.ts +24 -10
- package/maintenance.js +59 -28
- package/mcp-app.d.ts +7 -9
- package/mcp-app.js +7 -9
- package/mcp-modern.d.ts +14 -16
- package/mcp-modern.js +23 -27
- package/metrics.d.ts +41 -0
- package/metrics.js +22 -1
- package/node-server.d.ts +9 -9
- package/node-server.js +25 -34
- package/openapi.json +14675 -5957
- package/otel.d.ts +86 -0
- package/otel.js +273 -0
- package/overage.d.ts +33 -0
- package/overage.js +115 -0
- package/package-version.js +2 -4
- package/package.json +1 -1
- package/partner-billing.d.ts +81 -0
- package/partner-billing.js +333 -0
- package/partner-names.d.ts +8 -0
- package/partner-names.js +10 -0
- package/partners.d.ts +147 -0
- package/partners.js +375 -0
- package/plans.d.ts +92 -0
- package/plans.js +115 -0
- package/quotas.d.ts +108 -0
- package/quotas.js +258 -0
- package/rate-limit.d.ts +27 -2
- package/rate-limit.js +106 -23
- package/resource-profile.d.ts +135 -0
- package/resource-profile.js +263 -0
- package/rest-api.d.ts +15 -14
- package/rest-api.js +79 -15
- package/rules.d.ts +6 -7
- package/rules.js +30 -42
- package/sandbox.d.ts +70 -0
- package/sandbox.js +295 -0
- package/self-host.d.ts +46 -4
- package/self-host.js +990 -41
- package/slotlock.manifest.json +1 -1
- package/store.d.ts +482 -165
- package/store.js +1460 -484
- package/sync.d.ts +44 -59
- package/sync.js +325 -469
- package/teams.d.ts +25 -0
- package/teams.js +63 -0
- package/timezone.d.ts +25 -33
- package/timezone.js +50 -53
- package/types.d.ts +130 -57
- package/usage.d.ts +106 -0
- package/usage.js +238 -0
- package/webhooks.d.ts +22 -25
- package/webhooks.js +35 -40
- package/weekly-hours.d.ts +3 -0
- package/weekly-hours.js +1 -1
package/store.d.ts
CHANGED
|
@@ -1,19 +1,20 @@
|
|
|
1
|
+
import { type SlotlockResourceKind, type SlotlockResourceProfileInput, type SlotlockLocation, type SlotlockPoolOrder, type SlotlockResourceGroup, type SlotlockResourceScope } from './resource-profile.js';
|
|
2
|
+
import { type SlotlockUsageRow } from './usage.js';
|
|
1
3
|
import { type SlotlockChangeOrigin, type SlotlockEventFeed } from './event-feed.js';
|
|
2
4
|
import { type SlotlockBookingRules } from './booking-rules.js';
|
|
3
5
|
import type { Sql, TransactionSql } from 'postgres';
|
|
4
6
|
import { SLOTLOCK_CALENDAR_HORIZON_DAYS } from './rules.js';
|
|
5
|
-
import type { AcquireHoldResult, CalendarCoverageCommand, CalendarCoverageResult, CalendarEventContent, CalendarEventRetentionResult, BookingRetentionResult, SlotlockTenantErasure, SlotlockBookingRuleDefaults, CalendarFreeBusy, CalendarHorizonRollCursor, CalendarHorizonRollResult, CancelCalendarEventResult, CancelExternalReservationResult, ConfirmHoldResult, CreateReservationResult, DeleteResourceResult, ExternalReservationCommand, ExternalReservationResult, Interval, SlotlockCalendarEvent, SlotlockAgendaEntry, SlotlockHold, SlotlockReservation, SlotlockResource, PutCalendarEventCommand, PutCalendarEventResult, ReleaseHoldResult, WeeklyAvailabilityRule } from './types.js';
|
|
7
|
+
import type { AcquireHoldResult, CalendarCoverageCommand, CalendarCoverageResult, CalendarEventContent, CalendarEventRetentionResult, BookingRetentionResult, SlotlockTenantErasure, SlotlockBookingRuleDefaults, CalendarFreeBusy, CalendarHorizonRollCursor, CalendarHorizonRollResult, CancelCalendarEventResult, CancelExternalReservationResult, ConfirmHoldResult, CreateReservationResult, DeleteResourceResult, ExternalReservationCommand, ExternalReservationResult, Interval, SlotlockCalendarEvent, SlotlockAgendaEntry, SlotlockHold, SlotlockBookingDescription, SlotlockApproval, SlotlockApprovalPolicy, SlotlockReservation, SlotlockResource, PutCalendarEventCommand, PutCalendarEventResult, ReleaseHoldResult, WeeklyAvailabilityRule } from './types.js';
|
|
8
|
+
import { type PutApprovalPolicyParams } from './approvals.js';
|
|
6
9
|
export { SLOTLOCK_CALENDAR_HORIZON_DAYS };
|
|
7
10
|
/**
|
|
8
|
-
* Longest one-off event
|
|
9
|
-
*
|
|
10
|
-
* series is bounded by its 367-day materialization window instead.
|
|
11
|
+
* Longest one-off event (ten years and some leap days); a recurring series is bounded by its
|
|
12
|
+
* materialization window instead.
|
|
11
13
|
*/
|
|
12
14
|
export declare const SLOTLOCK_MAX_EVENT_DURATION_DAYS = 3660;
|
|
13
15
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* past its end, so that a search of the full 367 days from any moment of the day is complete.
|
|
16
|
+
* The 367-day search window plus two days: the rolling horizon starts a day before the current UTC
|
|
17
|
+
* day and must reach 367 days past its end.
|
|
17
18
|
*/
|
|
18
19
|
export declare const SLOTLOCK_MATERIALIZATION_WINDOW_DAYS: number;
|
|
19
20
|
/** Trusted provider/maintenance namespace used by backwards-compatible store callers. */
|
|
@@ -38,8 +39,8 @@ export declare const SLOTLOCK_EVENT_COMMAND_RETENTION_DAYS = 30;
|
|
|
38
39
|
/** Store-owned coverage source for bounded, locally materialized recurring events. */
|
|
39
40
|
export declare const SLOTLOCK_LOCAL_RECURRENCE_COVERAGE_SOURCE = "slotlock:local-recurrence-materialization";
|
|
40
41
|
/**
|
|
41
|
-
*
|
|
42
|
-
*
|
|
42
|
+
* From a day before the current UTC day to SLOTLOCK_MATERIALIZATION_WINDOW_DAYS past its end;
|
|
43
|
+
* shared by the writer, maintenance and readiness.
|
|
43
44
|
*/
|
|
44
45
|
export declare function calendarEventRollingHorizon(now?: Date): Interval;
|
|
45
46
|
/** What `createSlotlockStore` accepts: a `postgres` client, or a transaction/savepoint from one. */
|
|
@@ -48,9 +49,9 @@ type StoreSql = SlotlockSql;
|
|
|
48
49
|
/** How a migration (applySchema, applyTenantRls, grantApplicationRole) waits and how long it runs. */
|
|
49
50
|
export interface SlotlockMigrationOptions {
|
|
50
51
|
/**
|
|
51
|
-
* The longest it waits for any one lock, in milliseconds (1-600,000; default 1,000).
|
|
52
|
-
*
|
|
53
|
-
*
|
|
52
|
+
* The longest it waits for any one lock, in milliseconds (1-600,000; default 1,000). Queries on a
|
|
53
|
+
* table queue behind a migration waiting for its lock, so past this it gives up, lets them
|
|
54
|
+
* through, and retries.
|
|
54
55
|
*/
|
|
55
56
|
lockTimeoutMs?: number;
|
|
56
57
|
/** The longest any one of its statements runs, in milliseconds (1-86,400,000; default 900,000). */
|
|
@@ -59,21 +60,18 @@ export interface SlotlockMigrationOptions {
|
|
|
59
60
|
attempts?: number;
|
|
60
61
|
}
|
|
61
62
|
/**
|
|
62
|
-
* The schema version
|
|
63
|
-
*
|
|
64
|
-
* `slotlock migrate`, which grants it).
|
|
63
|
+
* The recorded schema version, or `null` when this role cannot read one: no schema, 0.2 or earlier,
|
|
64
|
+
* or the function not yet granted (`slotlock migrate` grants it).
|
|
65
65
|
*/
|
|
66
66
|
export declare function readSlotlockSchemaVersion(sql: SlotlockSql): Promise<number | null>;
|
|
67
67
|
/**
|
|
68
|
-
* Refuse
|
|
69
|
-
*
|
|
70
|
-
* migrated it, whose schema this release's code does not know. Both carry `installed` and `expected`.
|
|
68
|
+
* Refuse a schema another release migrated: `schema_outdated` (older or unversioned: run `slotlock
|
|
69
|
+
* migrate`) or `schema_newer`. Both carry `installed` and `expected`.
|
|
71
70
|
*/
|
|
72
71
|
export declare function assertSlotlockSchemaCurrent(sql: SlotlockSql): Promise<void>;
|
|
73
72
|
/**
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
* midnight: with it in the hash, a retry after midnight was refused as a different request.
|
|
73
|
+
* A put's identity and the event it asks for, not its storage window: a series' window moves with
|
|
74
|
+
* the UTC day, so a retry after midnight must still match.
|
|
77
75
|
*/
|
|
78
76
|
declare function putCommandHash(params: {
|
|
79
77
|
command: PutCalendarEventCommand;
|
|
@@ -81,9 +79,8 @@ declare function putCommandHash(params: {
|
|
|
81
79
|
event: CalendarEventContent;
|
|
82
80
|
}): string;
|
|
83
81
|
/**
|
|
84
|
-
* The hash 0.3.0 and earlier stored
|
|
85
|
-
*
|
|
86
|
-
* for a series. Remove once no stored command can be that old (commands are pruned after 30 days).
|
|
82
|
+
* The hash 0.3.0 and earlier stored (window included), so an older stored command still replays.
|
|
83
|
+
* Remove once no stored command can be that old (pruned after 30 days).
|
|
87
84
|
*/
|
|
88
85
|
export declare function legacyPutCommandHash(params: Parameters<typeof putCommandHash>[0] & {
|
|
89
86
|
window: Interval;
|
|
@@ -95,52 +92,38 @@ export declare const SLOTLOCK_MAX_POOL_RULE_DEFAULTS = 1000;
|
|
|
95
92
|
export declare function isPoolName(value: unknown): value is string;
|
|
96
93
|
export interface SlotlockStore extends SlotlockEventFeed {
|
|
97
94
|
/**
|
|
98
|
-
* Apply the Slotlock DDL (idempotent).
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
* role
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
* already migrated, unchanged since, is left as it is without a lock on any table. Constraints an
|
|
106
|
-
* upgrade adds NOT VALID are validated in a second transaction, whose locks no serving read or
|
|
107
|
-
* write waits for (one inside a caller's transaction shares its locks until it ends); one that old
|
|
108
|
-
* rows still break stays NOT VALID, and each later migration validates it again. Each lock it
|
|
109
|
-
* waits for is bounded by `lockTimeoutMs`, after which it tries again (`schema_lock_timeout` once
|
|
95
|
+
* Apply the Slotlock DDL (idempotent). Refuses (`unsafe_slotlock_schema`, with `reasons`) a
|
|
96
|
+
* `slotlock` schema that another role (superusers aside) owns, can create objects in, owns an
|
|
97
|
+
* object in, or can add triggers to; ownership through a group role counts, so deploy as the
|
|
98
|
+
* owning role itself (`SET ROLE`). Refuses (`schema_newer`) a schema a newer release migrated. An
|
|
99
|
+
* unchanged schema is left without locking any table. Constraints added NOT VALID are validated
|
|
100
|
+
* in a second transaction; one old rows still break stays NOT VALID and is retried on each
|
|
101
|
+
* migration. Each lock wait is bounded by `lockTimeoutMs` and retried (`schema_lock_timeout` once
|
|
110
102
|
* out of attempts).
|
|
111
103
|
*/
|
|
112
104
|
applySchema(options?: SlotlockMigrationOptions): Promise<void>;
|
|
113
105
|
/**
|
|
114
|
-
* Atomically enable forced tenant RLS using
|
|
115
|
-
*
|
|
116
|
-
* installs, bound to this setting, are left as they are without a lock on any table.
|
|
106
|
+
* Atomically enable forced tenant RLS using the store's context setting. Refuses the same schemas
|
|
107
|
+
* as `applySchema()`. Tables already set up exactly so are left without a lock.
|
|
117
108
|
*/
|
|
118
109
|
applyTenantRls(options?: {
|
|
119
110
|
allowRebind?: boolean;
|
|
120
111
|
} & SlotlockMigrationOptions): Promise<void>;
|
|
121
112
|
/**
|
|
122
113
|
* Grant an existing application role exactly what the store needs at runtime: USAGE on the
|
|
123
|
-
* `slotlock` schema and DML on its tables. Run as the deployment role after every
|
|
124
|
-
* Refuses (`unsafe_application_role`, with
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
-
* ownership of the database, of anything in it (its temporary objects and its own default
|
|
128
|
-
* privileges aside) or of `btree_gist`, CREATE on the database or on any schema, TRUNCATE,
|
|
129
|
-
* REFERENCES or TRIGGER on a Slotlock table (or by default on new ones), or a right on a server
|
|
130
|
-
* setting. The check runs when the grant is made, so a right added later is not
|
|
131
|
-
* detected until it runs again, and it is not exhaustive (SECURITY.md lists what it does not read).
|
|
114
|
+
* `slotlock` schema and DML on its tables. Run as the deployment role after every
|
|
115
|
+
* `applySchema()`. Refuses (`unsafe_application_role`, with `reasons`) a role with a known way
|
|
116
|
+
* around forced RLS or the exclusion arbiter, directly, by membership or through PUBLIC
|
|
117
|
+
* (SECURITY.md lists the checks and what they do not cover). The check runs only at grant time.
|
|
132
118
|
*/
|
|
133
119
|
grantApplicationRole(role: string, options?: SlotlockMigrationOptions): Promise<void>;
|
|
134
120
|
/**
|
|
135
121
|
* Run store operations with one transaction-local tenant context on the same connection. On a
|
|
136
|
-
* client it opens a READ COMMITTED transaction
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
* (grantApplicationRole grants it), and on a schema without it with `schema_outdated`. With the
|
|
142
|
-
* store's `maxTenantTransactions`, an aborted `signal` takes a call still waiting for its turn out
|
|
143
|
-
* of the line, refused with the signal's reason.
|
|
122
|
+
* client it opens a READ COMMITTED transaction. Inside a caller's transaction of another level,
|
|
123
|
+
* writes are refused (`invalid_transaction_isolation`), since that snapshot can miss a
|
|
124
|
+
* suspension. Needs EXECUTE on slotlock.tenant_suspended(text) (`application_role_not_granted`
|
|
125
|
+
* without it; grantApplicationRole grants it). With `maxTenantTransactions`, an aborted `signal`
|
|
126
|
+
* removes a waiting call from the line.
|
|
144
127
|
*/
|
|
145
128
|
withTenant<T>(tenantRef: string, callback: (store: SlotlockTenantStore) => Promise<T>, options?: {
|
|
146
129
|
isolation?: 'read committed';
|
|
@@ -157,19 +140,23 @@ export interface SlotlockStore extends SlotlockEventFeed {
|
|
|
157
140
|
origin?: SlotlockChangeOrigin;
|
|
158
141
|
/** The id (a UUID) of the API key making these changes, recorded with each. */
|
|
159
142
|
keyId?: string;
|
|
143
|
+
/**
|
|
144
|
+
* A key limited to part of its tenant (RFC 0003 G6): the transaction reaches only the
|
|
145
|
+
* resources at these locations (each with every location in it) or in these groups, as they
|
|
146
|
+
* are when it starts, by the database's resource_scope_v1 policies, on every table keyed by
|
|
147
|
+
* resource, for reads and writes. A resource outside it is not found.
|
|
148
|
+
*/
|
|
149
|
+
resourceScope?: SlotlockResourceScope;
|
|
160
150
|
}): Promise<T>;
|
|
161
151
|
/**
|
|
162
152
|
* INVARIANT (erasure completeness): a tenant integration must use a stable, erasable business
|
|
163
153
|
* identifier for externalRef and include this schema in its own data-erasure workflow.
|
|
164
154
|
*
|
|
165
|
-
* With `maxTenantResources` (1-100,000
|
|
166
|
-
*
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
*
|
|
170
|
-
* one itself, and inside a caller's transaction of another level it refuses
|
|
171
|
-
* (`invalid_transaction_isolation`). Uncapped creates do not wait for the lock and are not
|
|
172
|
-
* counted against a cap until they commit.
|
|
155
|
+
* With `maxTenantResources` (1-100,000) or the store's `resources` limit (the lower applies), a
|
|
156
|
+
* new resource is refused (`resource_limit_reached`) once the tenant has that many; an existing
|
|
157
|
+
* `externalRef` is still updated. Capped creates serialize on a database lock, so racing servers
|
|
158
|
+
* stop at the cap; this needs READ COMMITTED (`invalid_transaction_isolation` inside another
|
|
159
|
+
* level).
|
|
173
160
|
*/
|
|
174
161
|
createResource(params: {
|
|
175
162
|
externalRef?: string;
|
|
@@ -182,7 +169,103 @@ export interface SlotlockStore extends SlotlockEventFeed {
|
|
|
182
169
|
* refused (`resource_visibility_conflict`).
|
|
183
170
|
*/
|
|
184
171
|
visibility?: SlotlockResource['visibility'];
|
|
172
|
+
/**
|
|
173
|
+
* Its profile (resource-profile.ts): name, description, kind, capacity, labels and attributes,
|
|
174
|
+
* each optional. Creating an existing reference again sets the fields given and keeps the rest.
|
|
175
|
+
* Refused (`invalid_resource_profile`) outside its bounds, or for a resource with no tenant.
|
|
176
|
+
*/
|
|
177
|
+
profile?: SlotlockResourceProfileInput;
|
|
178
|
+
/**
|
|
179
|
+
* The reference of the location it is at (`location_not_found` when the tenant has none such).
|
|
180
|
+
* Without a `timezone`, a new resource takes the nearest time zone its location, or one above
|
|
181
|
+
* it, has, else UTC.
|
|
182
|
+
*/
|
|
183
|
+
location?: string;
|
|
185
184
|
}): Promise<SlotlockResource>;
|
|
185
|
+
/**
|
|
186
|
+
* Change one of the tenant's resources' profile: the fields given, keeping the rest (`null`
|
|
187
|
+
* clears a name, description or capacity). Null for an id that is not the tenant's; refused
|
|
188
|
+
* (`invalid_resource_profile`) outside its bounds. The feed's `resource.updated` names the
|
|
189
|
+
* fields that changed (`profile_changed`), never their values.
|
|
190
|
+
*/
|
|
191
|
+
updateResourceProfile(params: {
|
|
192
|
+
tenantRef: string;
|
|
193
|
+
id: string;
|
|
194
|
+
profile: SlotlockResourceProfileInput;
|
|
195
|
+
/** Move it to this location, or to none (`null`); left out, it stays (`location_not_found`). */
|
|
196
|
+
location?: string | null;
|
|
197
|
+
}): Promise<SlotlockResource | null>;
|
|
198
|
+
/**
|
|
199
|
+
* The tenant's locations, each under its parent, depth first (RFC 0003 G2), with how many
|
|
200
|
+
* resources are at each.
|
|
201
|
+
*/
|
|
202
|
+
listLocations(params: {
|
|
203
|
+
tenantRef: string;
|
|
204
|
+
}): Promise<SlotlockLocation[]>;
|
|
205
|
+
/**
|
|
206
|
+
* Create the location with this reference, or change its name, and the parent and time zone
|
|
207
|
+
* given. Refused: `invalid_location` (naming the field), `location_not_found` (no such parent),
|
|
208
|
+
* `location_too_deep` (past SLOTLOCK_MAX_LOCATION_DEPTH levels), `location_cycle` (under itself),
|
|
209
|
+
* `location_limit_reached` (SLOTLOCK_MAX_LOCATIONS).
|
|
210
|
+
*/
|
|
211
|
+
putLocation(params: {
|
|
212
|
+
tenantRef: string;
|
|
213
|
+
ref: string;
|
|
214
|
+
name: string;
|
|
215
|
+
parentRef?: string | null;
|
|
216
|
+
timezone?: string | null;
|
|
217
|
+
}): Promise<SlotlockLocation>;
|
|
218
|
+
/** Change a location's name, parent or time zone, as putLocation; null when it has none such. */
|
|
219
|
+
updateLocation(params: {
|
|
220
|
+
tenantRef: string;
|
|
221
|
+
ref: string;
|
|
222
|
+
name?: string;
|
|
223
|
+
parentRef?: string | null;
|
|
224
|
+
timezone?: string | null;
|
|
225
|
+
}): Promise<SlotlockLocation | null>;
|
|
226
|
+
/**
|
|
227
|
+
* Delete a location: false when it has none such; refused (`location_in_use`) while a resource is
|
|
228
|
+
* at it or a location is under it.
|
|
229
|
+
*/
|
|
230
|
+
deleteLocation(params: {
|
|
231
|
+
tenantRef: string;
|
|
232
|
+
ref: string;
|
|
233
|
+
}): Promise<boolean>;
|
|
234
|
+
/** The tenant's groups (RFC 0003 G3), by reference, with how many resources are in each. */
|
|
235
|
+
listGroups(params: {
|
|
236
|
+
tenantRef: string;
|
|
237
|
+
}): Promise<SlotlockResourceGroup[]>;
|
|
238
|
+
/**
|
|
239
|
+
* Create the group with this reference, or change its name, and its description when given.
|
|
240
|
+
* Refused: `invalid_group` (naming the field), `group_limit_reached` (SLOTLOCK_MAX_RESOURCE_GROUPS).
|
|
241
|
+
*/
|
|
242
|
+
putGroup(params: {
|
|
243
|
+
tenantRef: string;
|
|
244
|
+
ref: string;
|
|
245
|
+
name: string;
|
|
246
|
+
description?: string | null;
|
|
247
|
+
}): Promise<SlotlockResourceGroup>;
|
|
248
|
+
/** Delete a group, its resources left as they are: false when it has none such. */
|
|
249
|
+
deleteGroup(params: {
|
|
250
|
+
tenantRef: string;
|
|
251
|
+
ref: string;
|
|
252
|
+
}): Promise<boolean>;
|
|
253
|
+
/**
|
|
254
|
+
* Put one of the tenant's resources in a group: true when it was not in it. Refused:
|
|
255
|
+
* `group_not_found`, `resource_not_found`, `resource_group_limit_reached`
|
|
256
|
+
* (SLOTLOCK_MAX_GROUPS_PER_RESOURCE).
|
|
257
|
+
*/
|
|
258
|
+
addGroupMember(params: {
|
|
259
|
+
tenantRef: string;
|
|
260
|
+
group: string;
|
|
261
|
+
resourceId: string;
|
|
262
|
+
}): Promise<boolean>;
|
|
263
|
+
/** Take a resource out of a group: true when it was in it. */
|
|
264
|
+
removeGroupMember(params: {
|
|
265
|
+
tenantRef: string;
|
|
266
|
+
group: string;
|
|
267
|
+
resourceId: string;
|
|
268
|
+
}): Promise<boolean>;
|
|
186
269
|
/**
|
|
187
270
|
* Resolve one tenant-owned resource without revealing another tenant's matching id. An archived
|
|
188
271
|
* one is returned (with `archivedAt`); with `visibility`, only one of that visibility.
|
|
@@ -273,15 +356,55 @@ export interface SlotlockStore extends SlotlockEventFeed {
|
|
|
273
356
|
tenantRef: string;
|
|
274
357
|
pool: string;
|
|
275
358
|
visibility?: 'shared';
|
|
359
|
+
/** In the pool's order (`setPoolOrder`) rather than by id. */
|
|
360
|
+
ordered?: boolean;
|
|
361
|
+
/**
|
|
362
|
+
* For a booking about to take one (with `ordered`): a round-robin pool's place is locked until
|
|
363
|
+
* the transaction ends, so bookings at once go round in turn instead of starting from one place.
|
|
364
|
+
*/
|
|
365
|
+
forBooking?: boolean;
|
|
276
366
|
}): Promise<SlotlockResource[]>;
|
|
277
|
-
/**
|
|
367
|
+
/**
|
|
368
|
+
* The tenant's pools by name, with how many members each has that may be booked (its capacity)
|
|
369
|
+
* and the order a pool booking tries them in.
|
|
370
|
+
*/
|
|
278
371
|
listPools(params: {
|
|
279
372
|
tenantRef: string;
|
|
280
373
|
visibility?: 'shared';
|
|
281
374
|
}): Promise<{
|
|
282
375
|
pool: string;
|
|
283
376
|
capacity: number;
|
|
377
|
+
order: SlotlockPoolOrder;
|
|
284
378
|
}[]>;
|
|
379
|
+
/**
|
|
380
|
+
* Set the order a pool booking tries a pool's members in (RFC 0003 G7): `stable` (by id, the
|
|
381
|
+
* default), `least_booked` or `round_robin`: `invalid_pool` for a name that is not a pool's,
|
|
382
|
+
* `invalid_pool_order` for an order that is not one of these, `pool_order_limit_reached` past
|
|
383
|
+
* SLOTLOCK_MAX_POOL_SETTINGS pools a tenant.
|
|
384
|
+
*/
|
|
385
|
+
setPoolOrder(params: {
|
|
386
|
+
tenantRef: string;
|
|
387
|
+
pool: string;
|
|
388
|
+
order: SlotlockPoolOrder;
|
|
389
|
+
}): Promise<SlotlockPoolOrder>;
|
|
390
|
+
/**
|
|
391
|
+
* Make a resource a part of another (RFC 0003 G8), or no part (`null`): a booking of the whole
|
|
392
|
+
* then conflicts with every part, two parts never with each other. Null for no such resource;
|
|
393
|
+
* `parent_not_found`, `composite_too_deep` (one level only), `composite_parts_limit`
|
|
394
|
+
* (SLOTLOCK_MAX_COMPOSITE_PARTS) and `composite_busy` (either side has a booking or live hold to
|
|
395
|
+
* come) refuse it.
|
|
396
|
+
*/
|
|
397
|
+
setResourceParent(params: {
|
|
398
|
+
tenantRef: string;
|
|
399
|
+
id: string;
|
|
400
|
+
parentId: string | null;
|
|
401
|
+
}): Promise<SlotlockResource | null>;
|
|
402
|
+
/** Note the member a pool booking took, from which `round_robin` goes on. */
|
|
403
|
+
notePoolBooking(params: {
|
|
404
|
+
tenantRef: string;
|
|
405
|
+
pool: string;
|
|
406
|
+
resourceId: string;
|
|
407
|
+
}): Promise<void>;
|
|
285
408
|
/** Set (or clear, with `null`) the bookable hours of every resource the tenant has; returns how many. */
|
|
286
409
|
setTenantAvailability(params: {
|
|
287
410
|
tenantRef: string;
|
|
@@ -296,15 +419,49 @@ export interface SlotlockStore extends SlotlockEventFeed {
|
|
|
296
419
|
visibility?: SlotlockResource['visibility'];
|
|
297
420
|
/** Archived resources too; omitted, only those in use. */
|
|
298
421
|
includeArchived?: boolean;
|
|
422
|
+
/** Only resources of this kind (`other` for those that say none). */
|
|
423
|
+
kind?: SlotlockResourceKind;
|
|
424
|
+
/** Only resources with every one of these labels (at most 20). */
|
|
425
|
+
labels?: readonly string[];
|
|
426
|
+
/** Only resources at this location or a location under it. */
|
|
427
|
+
location?: string;
|
|
428
|
+
/** Only resources in this group. */
|
|
429
|
+
group?: string;
|
|
430
|
+
/** Only this pool's members. */
|
|
431
|
+
pool?: string;
|
|
299
432
|
}): Promise<SlotlockResource[]>;
|
|
300
433
|
/**
|
|
301
|
-
*
|
|
302
|
-
*
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
434
|
+
* How many resources the tenant has, archived ones included: the count a resource limit (a hosted
|
|
435
|
+
* plan's, `maxTenantResources`) is checked against.
|
|
436
|
+
*/
|
|
437
|
+
countResources(params: {
|
|
438
|
+
tenantRef: string;
|
|
439
|
+
}): Promise<number>;
|
|
440
|
+
/**
|
|
441
|
+
* Roll the tenant's committed bookings and holds up from its feed into its daily usage
|
|
442
|
+
* (usage.ts, `slotlock.roll_up_usage`), up to `limit` changes (1-100,000, default 5,000): how
|
|
443
|
+
* many it read. Fewer than `limit` means it has caught up.
|
|
444
|
+
*/
|
|
445
|
+
rollUpUsage(params: {
|
|
446
|
+
tenantRef: string;
|
|
447
|
+
limit?: number;
|
|
448
|
+
}): Promise<number>;
|
|
449
|
+
/**
|
|
450
|
+
* The tenant's usage from `from` to `to`, UTC days `YYYY-MM-DD` inclusive, at most
|
|
451
|
+
* SLOTLOCK_USAGE_RETENTION_DAYS apart (usage.ts, `slotlock.tenant_usage`): empty unless the
|
|
452
|
+
* deployment meters it.
|
|
453
|
+
*/
|
|
454
|
+
readUsage(params: {
|
|
455
|
+
tenantRef: string;
|
|
456
|
+
from: string;
|
|
457
|
+
to: string;
|
|
458
|
+
}): Promise<SlotlockUsageRow[]>;
|
|
459
|
+
/**
|
|
460
|
+
* Insert a confirmed reservation with the EXCLUDE constraint as the overlap arbiter; expected
|
|
461
|
+
* outcomes (invalid window, lost race) come back structured. Expired holds overlapping the window
|
|
462
|
+
* are reaped in the same transaction, so they never block. On a tenant's resource the booking
|
|
463
|
+
* takes the tenant and its own id as reference. With `tenantRef`, only that tenant's resource
|
|
464
|
+
* (`resource_not_found` otherwise, whatever the connection's RLS).
|
|
308
465
|
*/
|
|
309
466
|
createReservation(params: {
|
|
310
467
|
resourceId: string;
|
|
@@ -328,11 +485,9 @@ export interface SlotlockStore extends SlotlockEventFeed {
|
|
|
328
485
|
revision: number;
|
|
329
486
|
}): Promise<CancelExternalReservationResult>;
|
|
330
487
|
/**
|
|
331
|
-
* Occupy a window
|
|
332
|
-
*
|
|
333
|
-
*
|
|
334
|
-
* With `tenantRef`, only a resource of that tenant (`resource_not_found` otherwise, whatever the
|
|
335
|
-
* connection's row-level security).
|
|
488
|
+
* Occupy a window without committing: the hold blocks rivals under the same EXCLUDE arbiter until
|
|
489
|
+
* `expiresAt` (database clock: statement time + ttl), then frees via lazy reap. With `tenantRef`,
|
|
490
|
+
* only that tenant's resource (`resource_not_found` otherwise, whatever the connection's RLS).
|
|
336
491
|
*/
|
|
337
492
|
acquireHold(params: {
|
|
338
493
|
resourceId: string;
|
|
@@ -344,23 +499,28 @@ export interface SlotlockStore extends SlotlockEventFeed {
|
|
|
344
499
|
/** Turnaround kept busy after `end`, 0 to 30 days; the confirmed booking keeps it. */
|
|
345
500
|
bufferAfterMs?: number;
|
|
346
501
|
/**
|
|
347
|
-
* A retry identity, 1-500 UTF-8 bytes, unique
|
|
348
|
-
* required
|
|
349
|
-
*
|
|
350
|
-
* kept until the hold would have expired, released or confirmed; a retry after that makes a new
|
|
351
|
-
* hold.
|
|
502
|
+
* A retry identity, 1-500 UTF-8 bytes, unique within `tenantRef` and `principalRef` (both
|
|
503
|
+
* required): the same key and body answer the first hold (`idempotent: true`); another body is
|
|
504
|
+
* `idempotency_conflict`. Kept until the hold would have expired, released or confirmed.
|
|
352
505
|
*/
|
|
353
506
|
idempotencyKey?: string;
|
|
354
507
|
/** Whose keys `idempotencyKey` is among, 1-500 UTF-8 bytes (an API key's subject, say). */
|
|
355
508
|
principalRef?: string;
|
|
356
509
|
/**
|
|
357
|
-
* Who the hold belongs to, 1-500 UTF-8 bytes,
|
|
358
|
-
*
|
|
359
|
-
*
|
|
360
|
-
*
|
|
361
|
-
* holder and no cap: an application holding for its customers.
|
|
510
|
+
* Who the hold belongs to, 1-500 UTF-8 bytes, within `tenantRef` (required): `releaseHold` and
|
|
511
|
+
* `fromHold` with a `holderRef` take only their own, and a holder may have `maxHoldsPerHolder`
|
|
512
|
+
* live at once (and `maxHeldHoursPerHolder` together, when set). Without it the hold has no
|
|
513
|
+
* holder and no cap.
|
|
362
514
|
*/
|
|
363
515
|
holderRef?: string;
|
|
516
|
+
/**
|
|
517
|
+
* Lower the holder's caps for this hold (an API key's own limits, RFC 0004 A5): never above
|
|
518
|
+
* `maxHoldsPerHolder` and `maxHeldHoursPerHolder`, which win where they are lower.
|
|
519
|
+
*/
|
|
520
|
+
holderCaps?: {
|
|
521
|
+
maxHolds?: number;
|
|
522
|
+
maxHeldHours?: number;
|
|
523
|
+
};
|
|
364
524
|
/**
|
|
365
525
|
* Holds taken as alternatives for one booking share a group (a UUID): booking one with
|
|
366
526
|
* `fromHold` releases the rest, and releasing one with a holder releases them all.
|
|
@@ -372,6 +532,18 @@ export interface SlotlockStore extends SlotlockEventFeed {
|
|
|
372
532
|
tenantRef: string;
|
|
373
533
|
holdId: string;
|
|
374
534
|
}): Promise<SlotlockHold | null>;
|
|
535
|
+
/**
|
|
536
|
+
* One holder's own hold by id, lapsed or live (`live`), so a confirm can say `hold_expired`
|
|
537
|
+
* rather than not found. Null when it is gone, booked, or another holder's.
|
|
538
|
+
*/
|
|
539
|
+
getHolderHold(params: {
|
|
540
|
+
tenantRef: string;
|
|
541
|
+
holdId: string;
|
|
542
|
+
holderRef: string;
|
|
543
|
+
}): Promise<{
|
|
544
|
+
hold: SlotlockHold;
|
|
545
|
+
live: boolean;
|
|
546
|
+
} | null>;
|
|
375
547
|
/** The tenant's live holds on one resource, by start then id, keyset-paginated like events. */
|
|
376
548
|
listHolds(params: {
|
|
377
549
|
tenantRef: string;
|
|
@@ -401,6 +573,156 @@ export interface SlotlockStore extends SlotlockEventFeed {
|
|
|
401
573
|
tenantRef: string;
|
|
402
574
|
externalRef: string;
|
|
403
575
|
}): Promise<SlotlockReservation | null>;
|
|
576
|
+
/**
|
|
577
|
+
* Any booking, hold or event occurrence of the tenant's by its row id (the id a refused write's
|
|
578
|
+
* `conflictingReservationId` names): the time it occupies, buffers included, and whose it is.
|
|
579
|
+
* Null when the tenant has none, or the transaction's key may not see it.
|
|
580
|
+
*/
|
|
581
|
+
describeBooking(params: {
|
|
582
|
+
tenantRef: string;
|
|
583
|
+
id: string;
|
|
584
|
+
}): Promise<SlotlockBookingDescription | null>;
|
|
585
|
+
/**
|
|
586
|
+
* The time an owner's calendar events occupy now, buffers included, one row per occupation: what
|
|
587
|
+
* an approval (RFC 0004 A4) holds for a write, read in the write's own rolled-back transaction.
|
|
588
|
+
*/
|
|
589
|
+
eventOccupancy(params: {
|
|
590
|
+
tenantRef: string;
|
|
591
|
+
ownerRef: string;
|
|
592
|
+
externalRefs: readonly string[];
|
|
593
|
+
}): Promise<{
|
|
594
|
+
resourceId: string;
|
|
595
|
+
start: Date;
|
|
596
|
+
end: Date;
|
|
597
|
+
recurring: boolean;
|
|
598
|
+
}[]>;
|
|
599
|
+
/** Create or replace an approval policy; null when the tenant has its 50 already. */
|
|
600
|
+
putApprovalPolicy(params: PutApprovalPolicyParams): Promise<SlotlockApprovalPolicy | null>;
|
|
601
|
+
listApprovalPolicies(params: {
|
|
602
|
+
tenantRef: string;
|
|
603
|
+
}): Promise<SlotlockApprovalPolicy[]>;
|
|
604
|
+
deleteApprovalPolicy(params: {
|
|
605
|
+
tenantRef: string;
|
|
606
|
+
ref: string;
|
|
607
|
+
}): Promise<{
|
|
608
|
+
deleted: boolean;
|
|
609
|
+
}>;
|
|
610
|
+
/** The tenant's policies that apply to any of these resources (by id, pool, location, group, or all). */
|
|
611
|
+
approvalPoliciesFor(params: {
|
|
612
|
+
tenantRef: string;
|
|
613
|
+
resourceIds: readonly string[];
|
|
614
|
+
}): Promise<SlotlockApprovalPolicy[]>;
|
|
615
|
+
/**
|
|
616
|
+
* A write waiting for a person: its time held by holds the approval owns, until `expiresAt`, and
|
|
617
|
+
* the request kept to run as its requester when granted. A retry with the same `requestKey`
|
|
618
|
+
* answers the same approval (`replayed`), or `idempotency_conflict` for another request. With
|
|
619
|
+
* `consumeHold`, the requester's own hold of that time gives way to the approval's in the same
|
|
620
|
+
* transaction. `overlap` when the time is taken; `approval_limit_reached` past the pending caps.
|
|
621
|
+
*/
|
|
622
|
+
requestApproval(params: {
|
|
623
|
+
tenantRef: string;
|
|
624
|
+
policyRef: string;
|
|
625
|
+
operation: SlotlockApproval['operation'];
|
|
626
|
+
request: Record<string, unknown>;
|
|
627
|
+
requester: Record<string, unknown>;
|
|
628
|
+
requesterRef: string;
|
|
629
|
+
requestKey: string;
|
|
630
|
+
holds: readonly {
|
|
631
|
+
resourceId: string;
|
|
632
|
+
start: Date;
|
|
633
|
+
end: Date;
|
|
634
|
+
}[];
|
|
635
|
+
start: Date;
|
|
636
|
+
end: Date;
|
|
637
|
+
expiresAt: Date;
|
|
638
|
+
consumeHold?: {
|
|
639
|
+
holdId: string;
|
|
640
|
+
holderRef: string;
|
|
641
|
+
};
|
|
642
|
+
}): Promise<{
|
|
643
|
+
ok: true;
|
|
644
|
+
approval: SlotlockApproval;
|
|
645
|
+
replayed: boolean;
|
|
646
|
+
} | {
|
|
647
|
+
ok: false;
|
|
648
|
+
code: 'overlap' | 'approval_limit_reached' | 'idempotency_conflict';
|
|
649
|
+
limit?: number;
|
|
650
|
+
}>;
|
|
651
|
+
/** The approval a request key made, or null. */
|
|
652
|
+
findApproval(params: {
|
|
653
|
+
tenantRef: string;
|
|
654
|
+
requestKey: string;
|
|
655
|
+
}): Promise<SlotlockApproval | null>;
|
|
656
|
+
getApproval(params: {
|
|
657
|
+
tenantRef: string;
|
|
658
|
+
id: string;
|
|
659
|
+
}): Promise<(SlotlockApproval & {
|
|
660
|
+
requester: Record<string, unknown>;
|
|
661
|
+
}) | null>;
|
|
662
|
+
listApprovals(params: {
|
|
663
|
+
tenantRef: string;
|
|
664
|
+
status?: SlotlockApproval['status'];
|
|
665
|
+
limit?: number;
|
|
666
|
+
}): Promise<SlotlockApproval[]>;
|
|
667
|
+
/**
|
|
668
|
+
* Lock a pending, unexpired approval and let its holds go, so its write can take their time in
|
|
669
|
+
* the same transaction; finishApprovalGrant then marks it granted.
|
|
670
|
+
*/
|
|
671
|
+
beginApprovalGrant(params: {
|
|
672
|
+
tenantRef: string;
|
|
673
|
+
id: string;
|
|
674
|
+
}): Promise<{
|
|
675
|
+
ok: true;
|
|
676
|
+
approval: SlotlockApproval;
|
|
677
|
+
} | {
|
|
678
|
+
ok: false;
|
|
679
|
+
code: 'approval_not_found' | 'approval_not_pending' | 'approval_expired';
|
|
680
|
+
}>;
|
|
681
|
+
finishApprovalGrant(params: {
|
|
682
|
+
tenantRef: string;
|
|
683
|
+
id: string;
|
|
684
|
+
decidedBy: string;
|
|
685
|
+
}): Promise<SlotlockApproval>;
|
|
686
|
+
declineApproval(params: {
|
|
687
|
+
tenantRef: string;
|
|
688
|
+
id: string;
|
|
689
|
+
decidedBy: string;
|
|
690
|
+
reason?: string;
|
|
691
|
+
}): Promise<{
|
|
692
|
+
ok: true;
|
|
693
|
+
approval: SlotlockApproval;
|
|
694
|
+
} | {
|
|
695
|
+
ok: false;
|
|
696
|
+
code: 'approval_not_found' | 'approval_not_pending' | 'approval_expired';
|
|
697
|
+
}>;
|
|
698
|
+
/** Maintenance: up to `limit` pending approvals past their wait expire and free their time. */
|
|
699
|
+
expireApprovals(params: {
|
|
700
|
+
tenantRef: string;
|
|
701
|
+
limit?: number;
|
|
702
|
+
}): Promise<{
|
|
703
|
+
expired: number;
|
|
704
|
+
}>;
|
|
705
|
+
/**
|
|
706
|
+
* Empty a test environment (RFC 0004 A6): its resources, locations, groups, bookings, events,
|
|
707
|
+
* holds, waitlist, approval policies and feed; its keys, webhooks, plan and usage stay. Refused
|
|
708
|
+
* (`sandbox_test_only`) for a tenant that is not a test environment (`~test`). Rows removed, by
|
|
709
|
+
* table.
|
|
710
|
+
*/
|
|
711
|
+
resetTestTenant(params: {
|
|
712
|
+
tenantRef: string;
|
|
713
|
+
}): Promise<Record<string, number>>;
|
|
714
|
+
/**
|
|
715
|
+
* In a test environment, make what would lapse within `minutes` lapse now (RFC 0004 A6): holds
|
|
716
|
+
* (a waitlist offer's and an approval's among them) and pending approvals. Maintenance then
|
|
717
|
+
* reaps them as it would have. Refused (`sandbox_test_only`) elsewhere.
|
|
718
|
+
*/
|
|
719
|
+
fastForwardTestTenant(params: {
|
|
720
|
+
tenantRef: string;
|
|
721
|
+
minutes: number;
|
|
722
|
+
}): Promise<{
|
|
723
|
+
holds: number;
|
|
724
|
+
approvals: number;
|
|
725
|
+
}>;
|
|
404
726
|
/**
|
|
405
727
|
* The tenant's bookings on one resource whose occupied time meets `window` (at most 367 days),
|
|
406
728
|
* by start then id, keyset-paginated: pass the last one's `{ start, id }` as `after`.
|
|
@@ -416,12 +738,10 @@ export interface SlotlockStore extends SlotlockEventFeed {
|
|
|
416
738
|
};
|
|
417
739
|
}): Promise<SlotlockReservation[]>;
|
|
418
740
|
/**
|
|
419
|
-
*
|
|
420
|
-
*
|
|
421
|
-
*
|
|
422
|
-
*
|
|
423
|
-
* hold on one of that tenant's resources counts: another tenant's is `hold_not_found` whatever
|
|
424
|
-
* the connection's row-level security.
|
|
741
|
+
* Turn an unexpired hold into a confirmed booking. On a tenant's resource the booking takes the
|
|
742
|
+
* tenant and the hold's id as reference at revision 1 (`reservation_cancelled` when a cancel of
|
|
743
|
+
* that reference came first). Confirming again answers the booking as it now is. With
|
|
744
|
+
* `tenantRef`, another tenant's hold is `hold_not_found` whatever the connection's RLS.
|
|
425
745
|
*/
|
|
426
746
|
confirmHold(holdId: string, options?: SlotlockHoldTenantOptions): Promise<ConfirmHoldResult>;
|
|
427
747
|
/**
|
|
@@ -511,12 +831,10 @@ export interface SlotlockStore extends SlotlockEventFeed {
|
|
|
511
831
|
expectedResourceId?: string;
|
|
512
832
|
}): Promise<CancelCalendarEventResult>;
|
|
513
833
|
/**
|
|
514
|
-
*
|
|
515
|
-
*
|
|
516
|
-
*
|
|
517
|
-
* window
|
|
518
|
-
* too far past it for the 367-day cap from the window's start, is not due: it is left as it is
|
|
519
|
-
* and not counted.
|
|
834
|
+
* Rematerialize due recurring masters into one rolling window without changing their revision. A
|
|
835
|
+
* collision rolls back that master and keeps its old coverage. A master is rolled from the
|
|
836
|
+
* window's start through the later of its stored end and the window's; one already covering the
|
|
837
|
+
* window, or too far past it for the 367-day cap, is not due and not counted.
|
|
520
838
|
*/
|
|
521
839
|
rollCalendarEventHorizon(params: {
|
|
522
840
|
tenantRef: string;
|
|
@@ -526,12 +844,10 @@ export interface SlotlockStore extends SlotlockEventFeed {
|
|
|
526
844
|
after?: CalendarHorizonRollCursor;
|
|
527
845
|
}): Promise<CalendarHorizonRollResult>;
|
|
528
846
|
/**
|
|
529
|
-
* Delete one bounded batch of expired event bookkeeping
|
|
530
|
-
*
|
|
531
|
-
*
|
|
532
|
-
*
|
|
533
|
-
* waited on. After the window a retried command is evaluated afresh under its expected revision,
|
|
534
|
-
* and a pruned agent identity can be created again. Call until `hasMore` is false.
|
|
847
|
+
* Delete one bounded batch of expired event bookkeeping, which returns agent quota: commands
|
|
848
|
+
* older than the replay window (every owner), then agent-owned tombstones older than it with no
|
|
849
|
+
* remaining command. Active events and `internal` tombstones are never deleted. Locked rows are
|
|
850
|
+
* skipped. Afterwards a retried command is evaluated afresh. Call until `hasMore` is false.
|
|
535
851
|
*/
|
|
536
852
|
pruneCalendarEventRetention(params: {
|
|
537
853
|
tenantRef: string;
|
|
@@ -541,21 +857,17 @@ export interface SlotlockStore extends SlotlockEventFeed {
|
|
|
541
857
|
retentionDays?: number;
|
|
542
858
|
}): Promise<CalendarEventRetentionResult>;
|
|
543
859
|
/**
|
|
544
|
-
* Delete one bounded batch of a tenant's dead booking rows: holds
|
|
545
|
-
* answers
|
|
546
|
-
*
|
|
547
|
-
*
|
|
548
|
-
*
|
|
549
|
-
* tenant the tenant and its own id as reference, as the migration does. Call until `hasMore` is
|
|
550
|
-
* false.
|
|
860
|
+
* Delete one bounded batch of a tenant's dead booking rows: expired holds and their keyed
|
|
861
|
+
* answers, and references retired with no booking past retention. A cancelled booking's tombstone
|
|
862
|
+
* is only deleted by erasure, so its reference is never booked again. Locked rows are skipped.
|
|
863
|
+
* Also gives a confirmed booking a 0.2 server wrote without a tenant its tenant and reference, as
|
|
864
|
+
* the migration does. Call until `hasMore` is false.
|
|
551
865
|
*/
|
|
552
866
|
/**
|
|
553
|
-
*
|
|
554
|
-
*
|
|
555
|
-
*
|
|
556
|
-
*
|
|
557
|
-
* unless that overlaps another booking: then it stays and `event.timezone_conflict` reports it,
|
|
558
|
-
* for a person or an agent to decide. Up to `limit` (1-1,000, default 100) events a call.
|
|
867
|
+
* Find future one-off events whose wall-clock time now falls at another instant (new tzdata),
|
|
868
|
+
* each reported once (`event.timezone_shifted`). With `move`, it is moved (a new revision,
|
|
869
|
+
* `event.updated` by `slotlock:tzdata`) unless that overlaps another booking, which
|
|
870
|
+
* `event.timezone_conflict` reports instead. Up to `limit` (1-1,000, default 100) a call.
|
|
559
871
|
*/
|
|
560
872
|
followZoneRules(params: {
|
|
561
873
|
tenantRef: string;
|
|
@@ -644,6 +956,11 @@ export interface SlotlockStore extends SlotlockEventFeed {
|
|
|
644
956
|
end: Date;
|
|
645
957
|
holderRef: string;
|
|
646
958
|
offerTtlMs?: number;
|
|
959
|
+
/**
|
|
960
|
+
* A retry identity, kept as long as the entry: the same key and request answer the entry the
|
|
961
|
+
* first made (`replayed`), another request `idempotency_conflict`.
|
|
962
|
+
*/
|
|
963
|
+
idempotencyKey?: string;
|
|
647
964
|
}): Promise<JoinWaitlistResult>;
|
|
648
965
|
/** The tenant's waitlist entries, oldest first, optionally of one resource, pool or holder. */
|
|
649
966
|
listWaitlist(params: {
|
|
@@ -690,6 +1007,17 @@ export interface SlotlockStore extends SlotlockEventFeed {
|
|
|
690
1007
|
deleteAssistantSettings(params: {
|
|
691
1008
|
tenantRef: string;
|
|
692
1009
|
}): Promise<boolean>;
|
|
1010
|
+
/**
|
|
1011
|
+
* Replace the tenant's sealed assistant key `from` with `to`, the same key sealed under a newer
|
|
1012
|
+
* secret (SLOTLOCK_ASSISTANT_PREVIOUS_SECRETS), only while `from` is still the one stored: a key an
|
|
1013
|
+
* owner saved since is never overwritten. Nothing else changes, its hint and who set it included.
|
|
1014
|
+
* True when it was replaced.
|
|
1015
|
+
*/
|
|
1016
|
+
resealAssistantKey(params: {
|
|
1017
|
+
tenantRef: string;
|
|
1018
|
+
from: string;
|
|
1019
|
+
to: string;
|
|
1020
|
+
}): Promise<boolean>;
|
|
693
1021
|
/** A member's live assistant conversation, or null when none is (or it expired). */
|
|
694
1022
|
getAssistantConversation(params: {
|
|
695
1023
|
tenantRef: string;
|
|
@@ -740,12 +1068,11 @@ export interface SlotlockStore extends SlotlockEventFeed {
|
|
|
740
1068
|
limit?: number;
|
|
741
1069
|
}): Promise<number>;
|
|
742
1070
|
/**
|
|
743
|
-
* Offer freed time to the waitlist
|
|
744
|
-
*
|
|
745
|
-
*
|
|
746
|
-
*
|
|
747
|
-
*
|
|
748
|
-
* `next` back as `after` for the next, until it is null.
|
|
1071
|
+
* Offer freed time to the waitlist in join order: each waiting entry whose window is free (on its
|
|
1072
|
+
* resource, or the first free pool member) gets a hold for its holder (`waitlist.offered`).
|
|
1073
|
+
* Started windows expire (`waitlist.expired`); finished entries past retention are deleted. One
|
|
1074
|
+
* tenant's offers run one at a time. Pages of `limit` (1-100, default 50): pass `next` back as
|
|
1075
|
+
* `after` until null.
|
|
749
1076
|
*/
|
|
750
1077
|
offerWaitlist(params: {
|
|
751
1078
|
tenantRef: string;
|
|
@@ -773,11 +1100,10 @@ export interface SlotlockStore extends SlotlockEventFeed {
|
|
|
773
1100
|
retentionDays?: number;
|
|
774
1101
|
}): Promise<BookingRetentionResult>;
|
|
775
1102
|
/**
|
|
776
|
-
* Suspend a tenant: from when this returns,
|
|
777
|
-
*
|
|
778
|
-
*
|
|
779
|
-
*
|
|
780
|
-
* schema, may suspend (`tenant_suspension_not_permitted` for another).
|
|
1103
|
+
* Suspend a tenant: from when this returns, its withTenant transactions may read but writes are
|
|
1104
|
+
* refused (`tenant_suspended`); one in flight is waited for. Its keys still authenticate but it
|
|
1105
|
+
* gets no new key. Maintenance and erasure go on. True when it was not suspended already.
|
|
1106
|
+
* Deployment role only (`tenant_suspension_not_permitted`).
|
|
781
1107
|
*/
|
|
782
1108
|
suspendTenant(params: {
|
|
783
1109
|
tenantRef: string;
|
|
@@ -797,11 +1123,9 @@ export interface SlotlockStore extends SlotlockEventFeed {
|
|
|
797
1123
|
tenantRef: string;
|
|
798
1124
|
}): Promise<boolean>;
|
|
799
1125
|
/**
|
|
800
|
-
* Erase a tenant: delete every row it owns
|
|
801
|
-
*
|
|
802
|
-
*
|
|
803
|
-
* digests are retired so no erased key works again; a suspension stays. Erasing again deletes
|
|
804
|
-
* nothing.
|
|
1126
|
+
* Erase a tenant: delete every row it owns in one transaction, waiting for its transactions in
|
|
1127
|
+
* flight. API key digests are retired so no erased key works again; a suspension stays. Erasing
|
|
1128
|
+
* again deletes nothing.
|
|
805
1129
|
*/
|
|
806
1130
|
eraseTenant(params: {
|
|
807
1131
|
tenantRef: string;
|
|
@@ -817,12 +1141,11 @@ export interface SlotlockStore extends SlotlockEventFeed {
|
|
|
817
1141
|
coverageMaxAgeMs?: number;
|
|
818
1142
|
}): Promise<CalendarFreeBusy>;
|
|
819
1143
|
/**
|
|
820
|
-
* The first slot of `durationMs` inside the hours
|
|
821
|
-
* getFreeBusy × expandRules
|
|
822
|
-
*
|
|
823
|
-
*
|
|
824
|
-
*
|
|
825
|
-
* issues is `resource_not_found`. A search window longer than 367 days is `invalid_window`.
|
|
1144
|
+
* The first slot of `durationMs` inside the hours whose time and turnaround buffer are free
|
|
1145
|
+
* (getFreeBusy × expandRules in the resource's timezone → findNextAvailable). Null when no slot
|
|
1146
|
+
* is free or one cannot be proven free (coverage incomplete: a series not stored that far, or a
|
|
1147
|
+
* required provider not read that far). Unknown or another tenant's resource is
|
|
1148
|
+
* `resource_not_found`; a window over 367 days is `invalid_window`.
|
|
826
1149
|
*/
|
|
827
1150
|
findNextAvailableFor(params: {
|
|
828
1151
|
/** The resource's tenant; `null` for a resource that has none (the legacy namespace). */
|
|
@@ -882,9 +1205,10 @@ export interface SlotlockWaitlistEntry {
|
|
|
882
1205
|
export type JoinWaitlistResult = {
|
|
883
1206
|
ok: true;
|
|
884
1207
|
entry: SlotlockWaitlistEntry;
|
|
1208
|
+
replayed: boolean;
|
|
885
1209
|
} | {
|
|
886
1210
|
ok: false;
|
|
887
|
-
code: 'invalid_identity' | 'invalid_window' | 'invalid_ttl' | 'invalid_pool' | 'resource_not_found' | 'resource_archived' | 'pool_not_found' | 'waitlist_limit_reached';
|
|
1211
|
+
code: 'invalid_identity' | 'invalid_window' | 'invalid_ttl' | 'invalid_pool' | 'resource_not_found' | 'resource_archived' | 'pool_not_found' | 'waitlist_limit_reached' | 'idempotency_conflict';
|
|
888
1212
|
};
|
|
889
1213
|
export type CheckInReservationResult = {
|
|
890
1214
|
ok: true;
|
|
@@ -926,20 +1250,16 @@ export interface SlotlockStoreOptions {
|
|
|
926
1250
|
/** Hard retained-command ceiling per non-internal owner. Defaults to 10,000; cannot be disabled. */
|
|
927
1251
|
agentOwnerCommandQuota?: number;
|
|
928
1252
|
/**
|
|
929
|
-
* What one tenant may store, for a database
|
|
930
|
-
*
|
|
931
|
-
*
|
|
932
|
-
*
|
|
933
|
-
* only (`invalid_transaction_isolation` otherwise). Bookings and events count while they are still
|
|
934
|
-
* to come, and for 30 days after they were made, whatever time they hold; history older than that
|
|
935
|
-
* does not count, and a count stops reading at the limit.
|
|
1253
|
+
* What one tenant may store, for a shared database (none by default). A write that would pass a
|
|
1254
|
+
* limit is refused (`*_limit_reached`); existing rows can still be revised, cancelled and
|
|
1255
|
+
* released. Counted under a per-tenant lock in READ COMMITTED transactions only. Bookings and
|
|
1256
|
+
* events count while still to come and for 30 days after they were made.
|
|
936
1257
|
*/
|
|
937
1258
|
tenantLimits?: SlotlockTenantLimits;
|
|
938
1259
|
/**
|
|
939
|
-
* How many withTenant transactions of one tenant run at once on this store (1-1,000;
|
|
940
|
-
* default). The rest wait in
|
|
941
|
-
*
|
|
942
|
-
* transaction, which holds its connection already.
|
|
1260
|
+
* How many withTenant transactions of one tenant run at once on this store (1-1,000; unlimited by
|
|
1261
|
+
* default). The rest wait in order, so one tenant waiting on its own locks holds at most this
|
|
1262
|
+
* many pool connections. Not counted inside a caller's transaction.
|
|
943
1263
|
*/
|
|
944
1264
|
maxTenantTransactions?: number;
|
|
945
1265
|
/**
|
|
@@ -949,11 +1269,9 @@ export interface SlotlockStoreOptions {
|
|
|
949
1269
|
*/
|
|
950
1270
|
tenantLockTimeoutMs?: number;
|
|
951
1271
|
/**
|
|
952
|
-
*
|
|
953
|
-
*
|
|
954
|
-
*
|
|
955
|
-
* answered as the first try was). Maintenance prunes what is older. A cursor before a change any
|
|
956
|
-
* store has pruned answers `cursor_expired`, so stores set differently never skip a change.
|
|
1272
|
+
* Days history is kept, 1-3,650 (default 30): the change feed (and cursor reach) and ended
|
|
1273
|
+
* waitlist entries; and, when longer than 30 days, retired idempotency answers, booking
|
|
1274
|
+
* references and past events' commands. A cursor before a pruned change answers `cursor_expired`.
|
|
957
1275
|
*/
|
|
958
1276
|
retentionDays?: number;
|
|
959
1277
|
/**
|
|
@@ -972,10 +1290,9 @@ export interface SlotlockTenantLimits {
|
|
|
972
1290
|
/** Resources, archived ones included; the lower of this and a call's `maxTenantResources`. */
|
|
973
1291
|
resources?: number;
|
|
974
1292
|
/**
|
|
975
|
-
* Holds
|
|
976
|
-
*
|
|
977
|
-
*
|
|
978
|
-
* answers, as maintenance does, so only live holds keep a new one out.
|
|
1293
|
+
* Holds, expired ones included until deleted; a keyed hold keeps its place while its answer is
|
|
1294
|
+
* kept. At the limit, expired holds and their answers are deleted first, so only live holds keep
|
|
1295
|
+
* a new one out.
|
|
979
1296
|
*/
|
|
980
1297
|
holds?: number;
|
|
981
1298
|
/**
|
|
@@ -984,9 +1301,9 @@ export interface SlotlockTenantLimits {
|
|
|
984
1301
|
*/
|
|
985
1302
|
reservations?: number;
|
|
986
1303
|
/**
|
|
987
|
-
*
|
|
988
|
-
*
|
|
989
|
-
*
|
|
1304
|
+
* Events of every owner still to come (a series until known to have ended) or made in the last 30
|
|
1305
|
+
* days, cancellations made in the last 30 days, and agent cancellations until pruned. Each event
|
|
1306
|
+
* stores at most 2,000 occurrences.
|
|
990
1307
|
*/
|
|
991
1308
|
events?: number;
|
|
992
1309
|
/** Provider coverage rows: one per resource and source. */
|
|
@@ -1006,4 +1323,4 @@ export declare function createSlotlockStore(sql: StoreSql, options?: SlotlockSto
|
|
|
1006
1323
|
* Every store method that writes for a tenant: what a suspended tenant's withTenant store refuses.
|
|
1007
1324
|
* Maintenance (rolling series, pruning, purging) and erasure are not among them.
|
|
1008
1325
|
*/
|
|
1009
|
-
export declare const SLOTLOCK_TENANT_WRITE_METHODS: readonly ["createResource", "archiveResource", "restoreResource", "deleteResource", "setResourceAvailability", "setResourceBookingRules", "setBookingRuleDefaults", "setResourcePool", "setTenantAvailability", "createReservation", "createExternalReservation", "rescheduleExternalReservation", "cancelExternalReservation", "acquireHold", "confirmHold", "releaseHold", "putCalendarEvent", "cancelCalendarEvent", "cancelCalendarEventById", "recordCalendarCoverage", "createWebhookEndpoint", "deleteWebhookEndpoint", "enableWebhookEndpoint", "recordCoverageFailure", "deleteCoverage", "checkInReservation", "joinWaitlist", "leaveWaitlist", "putAssistantSettings", "deleteAssistantSettings", "putAssistantConversation", "settleAssistantAction", "deleteAssistantConversation"];
|
|
1326
|
+
export declare const SLOTLOCK_TENANT_WRITE_METHODS: readonly ["createResource", "updateResourceProfile", "putLocation", "updateLocation", "deleteLocation", "putGroup", "deleteGroup", "addGroupMember", "removeGroupMember", "archiveResource", "restoreResource", "deleteResource", "setResourceAvailability", "setResourceBookingRules", "setBookingRuleDefaults", "setResourcePool", "setResourceParent", "setPoolOrder", "notePoolBooking", "putApprovalPolicy", "deleteApprovalPolicy", "requestApproval", "beginApprovalGrant", "finishApprovalGrant", "declineApproval", "resetTestTenant", "fastForwardTestTenant", "setTenantAvailability", "createReservation", "createExternalReservation", "rescheduleExternalReservation", "cancelExternalReservation", "acquireHold", "confirmHold", "releaseHold", "putCalendarEvent", "cancelCalendarEvent", "cancelCalendarEventById", "recordCalendarCoverage", "createWebhookEndpoint", "deleteWebhookEndpoint", "enableWebhookEndpoint", "recordCoverageFailure", "deleteCoverage", "checkInReservation", "joinWaitlist", "leaveWaitlist", "putAssistantSettings", "deleteAssistantSettings", "resealAssistantKey", "putAssistantConversation", "settleAssistantAction", "deleteAssistantConversation"];
|