slotlock 0.5.2 → 0.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +98 -0
- package/README.md +97 -18
- package/SPEC.md +10 -7
- package/agent-server.d.ts +38 -4
- package/agent-server.js +464 -145
- package/agent-store-backend.d.ts +9 -0
- package/agent-store-backend.js +83 -15
- package/api-keys.d.ts +12 -2
- package/api-keys.js +36 -7
- package/application-api.d.ts +7 -1
- package/application-api.js +67 -7
- package/assistant.js +2 -1
- package/booking-rules.d.ts +61 -9
- package/booking-rules.js +106 -16
- package/change-watch.d.ts +56 -0
- package/change-watch.js +213 -0
- package/dashboard.d.ts +3 -1
- package/dashboard.js +98 -15
- package/ddl.d.ts +1 -1
- package/ddl.js +395 -4
- package/event-feed.d.ts +37 -4
- package/event-feed.js +74 -6
- package/index.d.ts +7 -6
- package/index.js +5 -4
- package/maintenance.js +12 -10
- package/openapi.json +1284 -82
- package/package.json +1 -1
- package/self-host.js +8 -4
- package/slotlock.manifest.json +1 -1
- package/store.d.ts +32 -3
- package/store.js +324 -124
- package/types.d.ts +14 -1
- package/webhooks.d.ts +9 -2
- package/webhooks.js +6 -1
package/package.json
CHANGED
package/self-host.js
CHANGED
|
@@ -8,7 +8,7 @@ import { createHash, timingSafeEqual } from 'node:crypto';
|
|
|
8
8
|
import { parseArgs } from 'node:util';
|
|
9
9
|
import postgres from 'postgres';
|
|
10
10
|
import { createSlotlockAgentServer, slotlockAgentOperationScope, } from './agent-server.js';
|
|
11
|
-
import { createSlotlockStoreAgentBackend, createSlotlockStoreApplicationBackend, } from './agent-store-backend.js';
|
|
11
|
+
import { createSlotlockStoreAgentBackend, createSlotlockStoreApplicationBackend, createSlotlockStoreChangeSource, } from './agent-store-backend.js';
|
|
12
12
|
import { SLOTLOCK_API_KEY_DEFAULT_SCOPES, SLOTLOCK_API_KEY_MAX_LIFETIME_DAYS, SLOTLOCK_API_KEY_SCOPES, createSlotlockApiKeyAuthenticator, createSlotlockApiKeyStore, slotlockBearerCredential, } from './api-keys.js';
|
|
13
13
|
import { createSlotlockDashboard, createSlotlockDashboardResources, createSlotlockAcceptanceRecords, createSlotlockDashboardState, } from './dashboard.js';
|
|
14
14
|
import { SECRET_HINT, SlotlockConfigError, formatSlotlockRate, optional, readAvailability, readDashboardConfig, readDashboardOrganizations, readLimits, readSecret, readServerUrl, readTrustedProxyHeader, slotlockKeySubscriptions, } from './config.js';
|
|
@@ -657,6 +657,8 @@ export async function startSlotlockServer(config, log) {
|
|
|
657
657
|
return true;
|
|
658
658
|
},
|
|
659
659
|
subscriptions: {
|
|
660
|
+
// Live updates poll each tenant's change feed (one reader per tenant).
|
|
661
|
+
changes: createSlotlockStoreChangeSource(store, sql),
|
|
660
662
|
maxTotal: caps.subscriptions,
|
|
661
663
|
maxPerTenant: caps.tenant_subscriptions,
|
|
662
664
|
maxPerPrincipal: slotlockKeySubscriptions(caps),
|
|
@@ -789,7 +791,9 @@ export async function startSlotlockServer(config, log) {
|
|
|
789
791
|
send,
|
|
790
792
|
feed: {
|
|
791
793
|
claimWebhookDeliveries: (params) => deliveryStore.withTenant(tenantRef, (tenant) => tenant.claimWebhookDeliveries(params)),
|
|
792
|
-
settleWebhookDelivery: (params) => deliveryStore.withTenant(tenantRef, (tenant) => tenant.settleWebhookDelivery(params)
|
|
794
|
+
settleWebhookDelivery: (params) => deliveryStore.withTenant(tenantRef, (tenant) => tenant.settleWebhookDelivery(params), {
|
|
795
|
+
origin: 'maintenance',
|
|
796
|
+
}),
|
|
793
797
|
},
|
|
794
798
|
}),
|
|
795
799
|
// Counts only, never the tenant or a URL.
|
|
@@ -1551,7 +1555,7 @@ export async function runSlotlockCli(argv, io) {
|
|
|
1551
1555
|
}
|
|
1552
1556
|
case 'key rotate': {
|
|
1553
1557
|
const config = readSlotlockDatabaseConfig(io.env);
|
|
1554
|
-
const rotated = await withApiKeys(config, (keys) => keys.rotate({ tenantRef: config.tenantRef, id: operands[1] }));
|
|
1558
|
+
const rotated = await withApiKeys(config, (keys) => keys.rotate({ tenantRef: config.tenantRef, id: operands[1], actor: CLI_KEY_CREATOR }));
|
|
1555
1559
|
if (!rotated) {
|
|
1556
1560
|
io.stderr.write('slotlock: no active API key with that id in this tenant\n');
|
|
1557
1561
|
return 1;
|
|
@@ -1562,7 +1566,7 @@ export async function runSlotlockCli(argv, io) {
|
|
|
1562
1566
|
}
|
|
1563
1567
|
case 'key revoke': {
|
|
1564
1568
|
const config = readSlotlockDatabaseConfig(io.env);
|
|
1565
|
-
const revoked = await withApiKeys(config, (keys) => keys.revoke({ tenantRef: config.tenantRef, id: operands[1] }));
|
|
1569
|
+
const revoked = await withApiKeys(config, (keys) => keys.revoke({ tenantRef: config.tenantRef, id: operands[1], actor: CLI_KEY_CREATOR }));
|
|
1566
1570
|
if (!revoked) {
|
|
1567
1571
|
io.stderr.write('slotlock: no API key with that id in this tenant\n');
|
|
1568
1572
|
return 1;
|
package/slotlock.manifest.json
CHANGED
package/store.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { type SlotlockEventFeed } from './event-feed.js';
|
|
1
|
+
import { type SlotlockChangeOrigin, type SlotlockEventFeed } from './event-feed.js';
|
|
2
2
|
import { type SlotlockBookingRules } from './booking-rules.js';
|
|
3
3
|
import type { Sql, TransactionSql } from 'postgres';
|
|
4
4
|
import { SLOTLOCK_CALENDAR_HORIZON_DAYS } from './rules.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';
|
|
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';
|
|
6
6
|
export { SLOTLOCK_CALENDAR_HORIZON_DAYS };
|
|
7
7
|
/**
|
|
8
8
|
* Longest one-off event the store accepts (ten years and some leap days): long enough for any lease
|
|
@@ -90,6 +90,8 @@ export declare function legacyPutCommandHash(params: Parameters<typeof putComman
|
|
|
90
90
|
}): string;
|
|
91
91
|
/** Members a pool booking or search considers at most, in id order. */
|
|
92
92
|
export declare const SLOTLOCK_MAX_POOL_MEMBERS = 100;
|
|
93
|
+
/** The most pools of one tenant that may have default booking rules (`setBookingRuleDefaults`). */
|
|
94
|
+
export declare const SLOTLOCK_MAX_POOL_RULE_DEFAULTS = 1000;
|
|
93
95
|
export declare function isPoolName(value: unknown): value is string;
|
|
94
96
|
export interface SlotlockStore extends SlotlockEventFeed {
|
|
95
97
|
/**
|
|
@@ -148,6 +150,13 @@ export interface SlotlockStore extends SlotlockEventFeed {
|
|
|
148
150
|
* without control characters: the history (listHistory) records it with each change.
|
|
149
151
|
*/
|
|
150
152
|
actor?: string;
|
|
153
|
+
/**
|
|
154
|
+
* Where the changes come from (SLOTLOCK_CHANGE_ORIGINS: `mcp`, `rest`, `dashboard`, ...),
|
|
155
|
+
* recorded with each beside the actor. Unset, the enclosing withTenant's, else `library`.
|
|
156
|
+
*/
|
|
157
|
+
origin?: SlotlockChangeOrigin;
|
|
158
|
+
/** The id (a UUID) of the API key making these changes, recorded with each. */
|
|
159
|
+
keyId?: string;
|
|
151
160
|
}): Promise<T>;
|
|
152
161
|
/**
|
|
153
162
|
* INVARIANT (erasure completeness): a tenant integration must use a stable, erasable business
|
|
@@ -228,6 +237,24 @@ export interface SlotlockStore extends SlotlockEventFeed {
|
|
|
228
237
|
id: string;
|
|
229
238
|
rules: SlotlockBookingRules | null;
|
|
230
239
|
}): Promise<SlotlockResource | null>;
|
|
240
|
+
/**
|
|
241
|
+
* Set (or clear, with `null`) the tenant's default booking rules, or one pool's (`pool`): a
|
|
242
|
+
* resource's rules as they apply (`effectiveBookingRules`) are the tenant's, overlaid by its
|
|
243
|
+
* pool's, overlaid by its own, a field set at a narrower level winning. They apply to bookings made
|
|
244
|
+
* from then on. `checkInMinutes` is only a resource's own (`invalid_booking_rules`, as for rules
|
|
245
|
+
* outside their bounds); `pool` is a pool name (`invalid_pool`). At most
|
|
246
|
+
* SLOTLOCK_MAX_POOL_RULE_DEFAULTS pools a tenant have them (`pool_rules_limit_reached` for one
|
|
247
|
+
* more). Returns the rules stored.
|
|
248
|
+
*/
|
|
249
|
+
setBookingRuleDefaults(params: {
|
|
250
|
+
tenantRef: string;
|
|
251
|
+
pool: string | null;
|
|
252
|
+
rules: SlotlockBookingRules | null;
|
|
253
|
+
}): Promise<SlotlockBookingRules | null>;
|
|
254
|
+
/** The tenant's default booking rules, and each pool's that has them, by pool name. */
|
|
255
|
+
getBookingRuleDefaults(params: {
|
|
256
|
+
tenantRef: string;
|
|
257
|
+
}): Promise<SlotlockBookingRuleDefaults>;
|
|
231
258
|
/**
|
|
232
259
|
* Put one resource in a pool, or take it out (`null`): `pool` is 1-100 characters of a-z, 0-9,
|
|
233
260
|
* `.`, `_` and `-`, starting with a letter or digit (`invalid_pool` otherwise). A resource is in
|
|
@@ -870,6 +897,8 @@ export type CheckInReservationResult = {
|
|
|
870
897
|
/** One source's coverage of a resource, with its health (listCoverage). */
|
|
871
898
|
export interface SlotlockCoverageHealth {
|
|
872
899
|
source: string;
|
|
900
|
+
/** How the change feed names the source (slotlockCoverageSourceId), never the source itself. */
|
|
901
|
+
sourceId: string;
|
|
873
902
|
start: Date;
|
|
874
903
|
end: Date;
|
|
875
904
|
revision: number;
|
|
@@ -974,4 +1003,4 @@ export declare function createSlotlockStore(sql: StoreSql, options?: SlotlockSto
|
|
|
974
1003
|
* Every store method that writes for a tenant: what a suspended tenant's withTenant store refuses.
|
|
975
1004
|
* Maintenance (rolling series, pruning, purging) and erasure are not among them.
|
|
976
1005
|
*/
|
|
977
|
-
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", "putAssistantSettings", "deleteAssistantSettings", "putAssistantConversation", "settleAssistantAction", "deleteAssistantConversation"];
|
|
1006
|
+
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"];
|