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/engine.d.ts
CHANGED
|
@@ -4,22 +4,19 @@ export declare function mergeIntervals(intervals: Interval[]): Interval[];
|
|
|
4
4
|
/** Free sub-intervals of `window` once `busy` is carved out (busy need not be sorted/merged). */
|
|
5
5
|
export declare function subtractBusy(window: Interval, busy: Interval[]): Interval[];
|
|
6
6
|
/**
|
|
7
|
-
* Earliest `[s, s + durationMs)`
|
|
8
|
-
*
|
|
9
|
-
* non-positive duration — a zero-width slot is never a useful answer).
|
|
7
|
+
* Earliest `[s, s + durationMs)` inside one of `windows` without touching `busy`, or null when
|
|
8
|
+
* nothing fits (including a non-positive duration).
|
|
10
9
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* shifts, no straddling" is unexpressible via touching windows — model it as a gap or a future
|
|
14
|
-
* constraint layer.
|
|
10
|
+
* Touching windows merge (RFC 7953 set-union), so a slot may span the seam of two abutting rules;
|
|
11
|
+
* "no straddling" must be modelled as a gap.
|
|
15
12
|
*/
|
|
16
13
|
export declare function findNextAvailable(params: {
|
|
17
14
|
busy: Interval[];
|
|
18
15
|
windows: Interval[];
|
|
19
16
|
durationMs: number;
|
|
20
17
|
/**
|
|
21
|
-
*
|
|
22
|
-
*
|
|
18
|
+
* The first allowed start at or after a candidate (a booking rule's step); must not move a start
|
|
19
|
+
* backwards.
|
|
23
20
|
*/
|
|
24
21
|
align?: (start: Date) => Date;
|
|
25
22
|
}): Interval | null;
|
package/engine.js
CHANGED
|
@@ -34,14 +34,11 @@ export function subtractBusy(window, busy) {
|
|
|
34
34
|
return free;
|
|
35
35
|
}
|
|
36
36
|
/**
|
|
37
|
-
* Earliest `[s, s + durationMs)`
|
|
38
|
-
*
|
|
39
|
-
* non-positive duration — a zero-width slot is never a useful answer).
|
|
37
|
+
* Earliest `[s, s + durationMs)` inside one of `windows` without touching `busy`, or null when
|
|
38
|
+
* nothing fits (including a non-positive duration).
|
|
40
39
|
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
* shifts, no straddling" is unexpressible via touching windows — model it as a gap or a future
|
|
44
|
-
* constraint layer.
|
|
40
|
+
* Touching windows merge (RFC 7953 set-union), so a slot may span the seam of two abutting rules;
|
|
41
|
+
* "no straddling" must be modelled as a gap.
|
|
45
42
|
*/
|
|
46
43
|
export function findNextAvailable(params) {
|
|
47
44
|
const { busy, windows, durationMs } = params;
|
|
@@ -49,8 +46,7 @@ export function findNextAvailable(params) {
|
|
|
49
46
|
return null;
|
|
50
47
|
const slotAt = (start) => ({ start, end: new Date(start.getTime() + durationMs) });
|
|
51
48
|
const align = params.align ?? ((start) => start);
|
|
52
|
-
// One walk of
|
|
53
|
-
// ending by a window's start ends before every later one too.
|
|
49
|
+
// One walk of merged busy alongside merged windows, both by start.
|
|
54
50
|
const merged = mergeIntervals(busy);
|
|
55
51
|
let next = 0;
|
|
56
52
|
for (const window of mergeIntervals(windows)) {
|
package/event-feed.d.ts
CHANGED
|
@@ -1,42 +1,36 @@
|
|
|
1
1
|
import type { Sql, TransactionSql } from 'postgres';
|
|
2
2
|
type SlotlockSql = Sql | TransactionSql;
|
|
3
3
|
/** What a change can be, as the feed and a webhook's `event_types` name it. */
|
|
4
|
-
export declare const SLOTLOCK_CHANGE_TYPES: readonly ["hold.created", "hold.updated", "hold.confirmed", "hold.released", "hold.expired", "reservation.created", "reservation.updated", "reservation.cancelled", "reservation.checked_in", "reservation.no_show", "event.created", "event.updated", "event.cancelled", "event.timezone_shifted", "event.timezone_conflict", "coverage.stale", "waitlist.offered", "waitlist.expired", "webhook.disabled", "resource.created", "resource.updated", "resource.archived", "resource.restored", "resource.deleted", "coverage.recorded", "coverage.deleted", "webhook.created", "webhook.enabled", "webhook.deleted", "key.created", "key.rotated", "key.revoked"];
|
|
4
|
+
export declare const SLOTLOCK_CHANGE_TYPES: readonly ["hold.created", "hold.updated", "hold.confirmed", "hold.released", "hold.expired", "reservation.created", "reservation.updated", "reservation.cancelled", "reservation.checked_in", "reservation.no_show", "event.created", "event.updated", "event.cancelled", "event.timezone_shifted", "event.timezone_conflict", "coverage.stale", "waitlist.offered", "waitlist.expired", "approval.requested", "approval.granted", "approval.declined", "approval.expired", "webhook.disabled", "usage.threshold_reached", "resource.created", "resource.updated", "resource.archived", "resource.restored", "resource.deleted", "coverage.recorded", "coverage.deleted", "webhook.created", "webhook.enabled", "webhook.deleted", "key.created", "key.rotated", "key.revoked"];
|
|
5
5
|
export type SlotlockChangeType = (typeof SLOTLOCK_CHANGE_TYPES)[number];
|
|
6
6
|
/**
|
|
7
|
-
* Where a change came from
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* store itself (`library`). Never an address or anything else about a person.
|
|
7
|
+
* Where a change came from (withTenant's `origin`): an agent protocol (`mcp`, `a2a`), the REST
|
|
8
|
+
* API's agent routes (`rest`) or application operations (`application`), the dashboard, its
|
|
9
|
+
* assistant, maintenance, or direct store calls (`library`). Never anything about a person.
|
|
11
10
|
*/
|
|
12
11
|
export declare const SLOTLOCK_CHANGE_ORIGINS: readonly ["mcp", "a2a", "rest", "application", "dashboard", "dashboard-assistant", "maintenance", "library"];
|
|
13
12
|
export type SlotlockChangeOrigin = (typeof SLOTLOCK_CHANGE_ORIGINS)[number];
|
|
14
13
|
/** Whether `value` is one of SLOTLOCK_CHANGE_ORIGINS. */
|
|
15
14
|
export declare function isSlotlockChangeOrigin(value: unknown): value is SlotlockChangeOrigin;
|
|
16
15
|
/**
|
|
17
|
-
* The digest 0.5.3 named a coverage source by
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* the upgrade uses this only to find the changes 0.5.3 wrote and rename them. `tenant` and `source`
|
|
21
|
-
* are SQL expressions.
|
|
16
|
+
* The unkeyed digest 0.5.3 named a coverage source by (`src_` + 24 hex of sha256(tenant, newline,
|
|
17
|
+
* source)); anyone knowing the tenant could confirm a guess. Used only by the upgrade to find and
|
|
18
|
+
* rename those changes. `tenant` and `source` are SQL expressions.
|
|
22
19
|
*/
|
|
23
20
|
export declare function legacyCoverageSourceDigestSql(tenant: string, source: string): string;
|
|
24
21
|
/**
|
|
25
|
-
* 0.
|
|
26
|
-
*
|
|
27
|
-
* only the database knows it, so this throws (`coverage_source_id_unavailable`) rather than give a
|
|
28
|
-
* digest nothing carries any more.
|
|
22
|
+
* Throws `coverage_source_id_unavailable`: since 0.6 a source's `source_id` is random and only the
|
|
23
|
+
* database knows it (0.5.3's unkeyed digest let anyone confirm a guessed source).
|
|
29
24
|
*
|
|
30
25
|
* @deprecated Read `sourceId` from `listCoverage` (`source_id` of `slotlock_get_coverage`). It may
|
|
31
26
|
* be removed in 0.7.0.
|
|
32
27
|
*/
|
|
33
28
|
export declare function slotlockCoverageSourceId(_tenantRef: string, _source: string): never;
|
|
34
29
|
/**
|
|
35
|
-
* A change's subject and data as the feed serves them. A `coverage.*` change never carries
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* earlier release's server writes during a rolling upgrade, is served redacted.
|
|
30
|
+
* A change's subject and data as the feed serves them. A `coverage.*` change never carries `source`
|
|
31
|
+
* or `last_error` (which can quote it), and is named by a current `source_id` or `src_redacted`:
|
|
32
|
+
* rows an earlier release wrote are renamed in batches after upgrade, so one not reached yet is
|
|
33
|
+
* served redacted.
|
|
40
34
|
*/
|
|
41
35
|
export declare function slotlockServedChange(type: string, subject: string, data: Record<string, unknown>): {
|
|
42
36
|
subject: string;
|
|
@@ -45,7 +39,7 @@ export declare function slotlockServedChange(type: string, subject: string, data
|
|
|
45
39
|
/** How long the feed keeps a change, and so how far back a cursor may reach. */
|
|
46
40
|
export declare const SLOTLOCK_CHANGE_RETENTION_DAYS = 30;
|
|
47
41
|
/** Webhook endpoints one tenant may have. */
|
|
48
|
-
export declare const SLOTLOCK_MAX_WEBHOOK_ENDPOINTS =
|
|
42
|
+
export declare const SLOTLOCK_MAX_WEBHOOK_ENDPOINTS = 25;
|
|
49
43
|
/** After this long failing, an endpoint is disabled and the feed says so (`webhook.disabled`). */
|
|
50
44
|
export declare const SLOTLOCK_WEBHOOK_FAILING_LIMIT_MS: number;
|
|
51
45
|
export interface SlotlockChange {
|
|
@@ -66,11 +60,29 @@ export interface SlotlockChange {
|
|
|
66
60
|
/** Pass as `after` to read what follows this change. */
|
|
67
61
|
position: string;
|
|
68
62
|
}
|
|
63
|
+
/** Resources one webhook endpoint's filter may name. */
|
|
64
|
+
export declare const SLOTLOCK_WEBHOOK_FILTER_RESOURCES = 100;
|
|
65
|
+
/** Pools, locations or groups one webhook endpoint's filter may name, of each. */
|
|
66
|
+
export declare const SLOTLOCK_WEBHOOK_FILTER_REFS = 20;
|
|
67
|
+
/**
|
|
68
|
+
* Which resources' changes an endpoint receives (RFC 0003 G5): only changes about a resource that
|
|
69
|
+
* matches one of these, as it is when the change is delivered, so a change about no resource (a key,
|
|
70
|
+
* a webhook, a usage notice) is not sent. A location includes every location in it. At most
|
|
71
|
+
* SLOTLOCK_WEBHOOK_FILTER_RESOURCES ids, and SLOTLOCK_WEBHOOK_FILTER_REFS of each of the others.
|
|
72
|
+
*/
|
|
73
|
+
export interface SlotlockWebhookFilter {
|
|
74
|
+
resourceIds?: string[];
|
|
75
|
+
pools?: string[];
|
|
76
|
+
locations?: string[];
|
|
77
|
+
groups?: string[];
|
|
78
|
+
}
|
|
69
79
|
export interface SlotlockWebhookEndpoint {
|
|
70
80
|
id: string;
|
|
71
81
|
url: string;
|
|
72
82
|
/** Null: every type. */
|
|
73
83
|
eventTypes: SlotlockChangeType[] | null;
|
|
84
|
+
/** Only changes about these resources; absent, changes about any resource or none. */
|
|
85
|
+
filter?: SlotlockWebhookFilter;
|
|
74
86
|
createdAt: Date;
|
|
75
87
|
/** Failed attempts since the last delivery that succeeded. */
|
|
76
88
|
attempts: number;
|
|
@@ -96,10 +108,11 @@ export interface SlotlockWebhookClaim {
|
|
|
96
108
|
export type CreateWebhookEndpointResult = {
|
|
97
109
|
ok: true;
|
|
98
110
|
endpoint: SlotlockWebhookEndpoint;
|
|
99
|
-
secret: string;
|
|
111
|
+
secret: string; /** True when an idempotency key answered what an earlier create made. */
|
|
112
|
+
replayed?: boolean;
|
|
100
113
|
} | {
|
|
101
114
|
ok: false;
|
|
102
|
-
code: 'invalid_webhook_url' | 'invalid_event_types';
|
|
115
|
+
code: 'invalid_webhook_url' | 'invalid_event_types' | 'invalid_webhook_filter' | 'idempotency_conflict' | 'invalid_identity';
|
|
103
116
|
} | {
|
|
104
117
|
ok: false;
|
|
105
118
|
code: 'webhook_limit_reached';
|
|
@@ -115,11 +128,10 @@ export type ListChangesResult = {
|
|
|
115
128
|
};
|
|
116
129
|
export interface SlotlockEventFeed {
|
|
117
130
|
/**
|
|
118
|
-
* The tenant's changes after `after` (
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
* any store has pruned is `cursor_expired`: read from the start again.
|
|
131
|
+
* The tenant's changes after `after` (or from the oldest kept), oldest first, up to `limit`
|
|
132
|
+
* (1-500, default 100). Only ended transactions are read, so a reader following `next` never
|
|
133
|
+
* skips a late commit. `next` is the last position, or `after` when there is none: poll with it.
|
|
134
|
+
* A position before a pruned change is `cursor_expired`.
|
|
123
135
|
*/
|
|
124
136
|
listChanges(params: {
|
|
125
137
|
tenantRef: string;
|
|
@@ -143,6 +155,13 @@ export interface SlotlockEventFeed {
|
|
|
143
155
|
tenantRef: string;
|
|
144
156
|
url: string;
|
|
145
157
|
eventTypes?: readonly string[];
|
|
158
|
+
/** Only changes about these resources (`invalid_webhook_filter` past its bounds). */
|
|
159
|
+
filter?: SlotlockWebhookFilter;
|
|
160
|
+
/**
|
|
161
|
+
* A retry identity, kept as long as the endpoint: the same key and request answer the endpoint
|
|
162
|
+
* and secret the first made (`replayed`), another request `idempotency_conflict`.
|
|
163
|
+
*/
|
|
164
|
+
idempotencyKey?: string;
|
|
146
165
|
}): Promise<CreateWebhookEndpointResult>;
|
|
147
166
|
listWebhookEndpoints(params: {
|
|
148
167
|
tenantRef: string;
|
|
@@ -165,9 +184,9 @@ export interface SlotlockEventFeed {
|
|
|
165
184
|
code: 'changes_pruned';
|
|
166
185
|
}>;
|
|
167
186
|
/**
|
|
168
|
-
* Lease the tenant's endpoints
|
|
169
|
-
*
|
|
170
|
-
*
|
|
187
|
+
* Lease the tenant's endpoints with changes due, each with its next `batch` (1-100) changes, for
|
|
188
|
+
* `leaseMs` (1,000-300,000). An expired lease is claimed again by any worker, so a crash costs a
|
|
189
|
+
* redelivery, never a lost change.
|
|
171
190
|
*/
|
|
172
191
|
claimWebhookDeliveries(params: {
|
|
173
192
|
tenantRef: string;
|
|
@@ -193,11 +212,10 @@ export interface SlotlockEventFeed {
|
|
|
193
212
|
};
|
|
194
213
|
}): Promise<void>;
|
|
195
214
|
/**
|
|
196
|
-
* History
|
|
197
|
-
*
|
|
198
|
-
*
|
|
199
|
-
*
|
|
200
|
-
* `id` as `before` for the page after. Kept as long as the feed keeps changes.
|
|
215
|
+
* History of one resource (`resourceId`: its bookings, events and configuration) or one record
|
|
216
|
+
* (`subject`), newest first, `limit` (1-100) at a time, each with `actor`, `keyId`, `origin` and
|
|
217
|
+
* the prior state where there was one. Pass the last `id` as `before` for the next page. Kept as
|
|
218
|
+
* long as the feed.
|
|
201
219
|
*/
|
|
202
220
|
listHistory(params: {
|
|
203
221
|
tenantRef: string;
|