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/event-feed.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// endpoints it is delivered to. Rows are appended by triggers in the same transaction as the change
|
|
3
3
|
// (ddl.ts, SLOTLOCK_OUTBOX_DDL). These are the store's methods for reading them and for keeping each
|
|
4
4
|
// endpoint's delivery state; webhooks.ts signs and sends.
|
|
5
|
-
import { randomBytes } from 'node:crypto';
|
|
5
|
+
import { createHash, randomBytes } from 'node:crypto';
|
|
6
6
|
/** A transaction on a client, or a savepoint inside the caller's (a tenant's, say). */
|
|
7
7
|
async function inTransaction(sql, callback) {
|
|
8
8
|
return ('begin' in sql ? sql.begin(callback) : sql.savepoint(callback));
|
|
@@ -28,7 +28,58 @@ export const SLOTLOCK_CHANGE_TYPES = Object.freeze([
|
|
|
28
28
|
'waitlist.offered',
|
|
29
29
|
'waitlist.expired',
|
|
30
30
|
'webhook.disabled',
|
|
31
|
+
// Configuration: what a resource is and how it books, the sources it is read from, where the
|
|
32
|
+
// feed is delivered and the keys that reach the tenant. Ids and states, never a secret or a URL.
|
|
33
|
+
'resource.created',
|
|
34
|
+
'resource.updated',
|
|
35
|
+
'resource.archived',
|
|
36
|
+
'resource.restored',
|
|
37
|
+
'resource.deleted',
|
|
38
|
+
'coverage.recorded',
|
|
39
|
+
'coverage.deleted',
|
|
40
|
+
'webhook.created',
|
|
41
|
+
'webhook.enabled',
|
|
42
|
+
'webhook.deleted',
|
|
43
|
+
'key.created',
|
|
44
|
+
'key.rotated',
|
|
45
|
+
'key.revoked',
|
|
31
46
|
]);
|
|
47
|
+
/**
|
|
48
|
+
* Where a change came from, as the history and the feed record it (withTenant's `origin`): an agent
|
|
49
|
+
* protocol (`mcp`, `a2a`), the REST API's agent routes (`rest`) or its application operations
|
|
50
|
+
* (`application`), the dashboard, a member's dashboard assistant, maintenance, or code calling the
|
|
51
|
+
* store itself (`library`). Never an address or anything else about a person.
|
|
52
|
+
*/
|
|
53
|
+
export const SLOTLOCK_CHANGE_ORIGINS = Object.freeze([
|
|
54
|
+
'mcp',
|
|
55
|
+
'a2a',
|
|
56
|
+
'rest',
|
|
57
|
+
'application',
|
|
58
|
+
'dashboard',
|
|
59
|
+
'dashboard-assistant',
|
|
60
|
+
'maintenance',
|
|
61
|
+
'library',
|
|
62
|
+
]);
|
|
63
|
+
/** Whether `value` is one of SLOTLOCK_CHANGE_ORIGINS. */
|
|
64
|
+
export function isSlotlockChangeOrigin(value) {
|
|
65
|
+
return typeof value === 'string' && SLOTLOCK_CHANGE_ORIGINS.includes(value);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* The SQL that names a coverage source on the feed (`coverage.recorded`, `.deleted`, `.stale`): a
|
|
69
|
+
* digest of the tenant and the source, stable for both, so the source itself (an iCalendar URL with
|
|
70
|
+
* a token, an address) never reaches a webhook or the history. `tenant` and `source` are SQL
|
|
71
|
+
* expressions; slotlockCoverageSourceId works out the same value.
|
|
72
|
+
*/
|
|
73
|
+
export function slotlockCoverageSourceIdSql(tenant, source) {
|
|
74
|
+
return `'src_' || left(encode(sha256(convert_to(${tenant} || E'\\n' || ${source}, 'UTF8')), 'hex'), 24)`;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* How the feed names a tenant's coverage source (`subject` and `data.source_id` of `coverage.*`
|
|
78
|
+
* changes; `source_id` of slotlock_get_coverage): `src_` and 24 hex digits.
|
|
79
|
+
*/
|
|
80
|
+
export function slotlockCoverageSourceId(tenantRef, source) {
|
|
81
|
+
return `src_${createHash('sha256').update(`${tenantRef}\n${source}`, 'utf8').digest('hex').slice(0, 24)}`;
|
|
82
|
+
}
|
|
32
83
|
/** How long the feed keeps a change, and so how far back a cursor may reach. */
|
|
33
84
|
export const SLOTLOCK_CHANGE_RETENTION_DAYS = 30;
|
|
34
85
|
/** Webhook endpoints one tenant may have. */
|
|
@@ -58,6 +109,9 @@ function toChange(row) {
|
|
|
58
109
|
data: row.data,
|
|
59
110
|
occurredAt: row.occurred_at,
|
|
60
111
|
actor: row.actor,
|
|
112
|
+
// One a later release added, read by this one: unknown here, so unrecorded, not a failed page.
|
|
113
|
+
origin: isSlotlockChangeOrigin(row.origin) ? row.origin : null,
|
|
114
|
+
keyId: row.key_id,
|
|
61
115
|
position: encodePosition({ txid: row.txid, id: row.id, at: row.occurred_at.getTime() }),
|
|
62
116
|
};
|
|
63
117
|
}
|
|
@@ -127,7 +181,7 @@ export function slotlockEventFeedMethods(sql, retentionDays = SLOTLOCK_CHANGE_RE
|
|
|
127
181
|
SELECT mark.txid::text AS pruned_txid, mark.id::text AS pruned_id,
|
|
128
182
|
pg_snapshot_xmin(pg_current_snapshot())::text AS horizon,
|
|
129
183
|
page.id::text, page.txid::text, page.type, page.resource_id, page.subject,
|
|
130
|
-
page.data, page.occurred_at, page.actor
|
|
184
|
+
page.data, page.occurred_at, page.actor, page.origin, page.key_id::text
|
|
131
185
|
FROM (SELECT 1) here
|
|
132
186
|
LEFT JOIN slotlock.outbox_pruned mark ON mark.tenant_ref = ${params.tenantRef}
|
|
133
187
|
LEFT JOIN LATERAL (
|
|
@@ -179,7 +233,8 @@ export function slotlockEventFeedMethods(sql, retentionDays = SLOTLOCK_CHANGE_RE
|
|
|
179
233
|
});
|
|
180
234
|
}
|
|
181
235
|
const rows = await sql `
|
|
182
|
-
SELECT id::text, txid::text, type, resource_id, subject, data, occurred_at, actor
|
|
236
|
+
SELECT id::text, txid::text, type, resource_id, subject, data, occurred_at, actor, origin,
|
|
237
|
+
key_id::text
|
|
183
238
|
FROM slotlock.outbox
|
|
184
239
|
WHERE tenant_ref = ${params.tenantRef}
|
|
185
240
|
ORDER BY txid DESC, id DESC
|
|
@@ -194,7 +249,6 @@ export function slotlockEventFeedMethods(sql, retentionDays = SLOTLOCK_CHANGE_RE
|
|
|
194
249
|
const types = params.eventTypes === undefined ? null : [...new Set(params.eventTypes)];
|
|
195
250
|
if (types !== null &&
|
|
196
251
|
(types.length === 0 ||
|
|
197
|
-
types.length > 20 ||
|
|
198
252
|
!types.every((type) => SLOTLOCK_CHANGE_TYPES.includes(type)))) {
|
|
199
253
|
return { ok: false, code: 'invalid_event_types' };
|
|
200
254
|
}
|
|
@@ -300,7 +354,8 @@ export function slotlockEventFeedMethods(sql, retentionDays = SLOTLOCK_CHANGE_RE
|
|
|
300
354
|
const claims = [];
|
|
301
355
|
for (const endpoint of endpoints) {
|
|
302
356
|
const rows = await sql `
|
|
303
|
-
SELECT id::text, txid::text, type, resource_id, subject, data, occurred_at, actor
|
|
357
|
+
SELECT id::text, txid::text, type, resource_id, subject, data, occurred_at, actor, origin,
|
|
358
|
+
key_id::text
|
|
304
359
|
FROM slotlock.outbox
|
|
305
360
|
WHERE tenant_ref = ${params.tenantRef}
|
|
306
361
|
AND txid < pg_snapshot_xmin(pg_current_snapshot())
|
|
@@ -393,7 +448,8 @@ export function slotlockEventFeedMethods(sql, retentionDays = SLOTLOCK_CHANGE_RE
|
|
|
393
448
|
if (before === undefined)
|
|
394
449
|
return [];
|
|
395
450
|
const rows = await sql `
|
|
396
|
-
SELECT id::text, txid::text, type, resource_id, subject, data, occurred_at, actor
|
|
451
|
+
SELECT id::text, txid::text, type, resource_id, subject, data, occurred_at, actor, origin,
|
|
452
|
+
key_id::text
|
|
397
453
|
FROM slotlock.outbox
|
|
398
454
|
WHERE tenant_ref = ${params.tenantRef}
|
|
399
455
|
AND (${params.resourceId ?? null}::uuid IS NULL OR resource_id = ${params.resourceId ?? null}::uuid)
|
|
@@ -445,3 +501,15 @@ export function slotlockEventFeedMethods(sql, retentionDays = SLOTLOCK_CHANGE_RE
|
|
|
445
501
|
},
|
|
446
502
|
};
|
|
447
503
|
}
|
|
504
|
+
/**
|
|
505
|
+
* A position at or before every change that has not ended yet, so a reader that starts from it and
|
|
506
|
+
* follows `next` misses none still to come (it may read again some that ended just before). Live
|
|
507
|
+
* updates start here (agent-store-backend.ts, createSlotlockStoreChangeSource).
|
|
508
|
+
*/
|
|
509
|
+
export async function slotlockChangeFeedHead(sql) {
|
|
510
|
+
const [row] = await sql `
|
|
511
|
+
SELECT pg_snapshot_xmin(pg_current_snapshot())::text AS horizon`;
|
|
512
|
+
if (!row)
|
|
513
|
+
throw new Error('slotlock: change feed head returned no row');
|
|
514
|
+
return encodePosition({ txid: row.horizon, id: '0', at: Date.now() });
|
|
515
|
+
}
|
package/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/// <reference types="node" preserve="true" />
|
|
2
2
|
export { findNextAvailable, mergeIntervals, subtractBusy } from './engine.js';
|
|
3
|
-
export { type SlotlockBookingRule, type SlotlockBookingRules, bookingRuleBroken, bookingSearch, nextStepStart, parseBookingRules, } from './booking-rules.js';
|
|
4
|
-
export { type CreateWebhookEndpointResult, type ListChangesResult, SLOTLOCK_CHANGE_RETENTION_DAYS, SLOTLOCK_CHANGE_TYPES, SLOTLOCK_MAX_WEBHOOK_ENDPOINTS, SLOTLOCK_WEBHOOK_FAILING_LIMIT_MS, type SlotlockChange, type SlotlockChangeType, type SlotlockEventFeed, type SlotlockWebhookClaim, type SlotlockWebhookEndpoint, } from './event-feed.js';
|
|
3
|
+
export { type SlotlockBookingRule, type SlotlockBookingRules, type SlotlockBookingSearch, bookingRuleBroken, bookingSearch, bookingSearchBlocked, bookingSearchReach, nextStepStart, parseBookingRuleDefaults, parseBookingRules, } from './booking-rules.js';
|
|
4
|
+
export { type CreateWebhookEndpointResult, type ListChangesResult, SLOTLOCK_CHANGE_ORIGINS, SLOTLOCK_CHANGE_RETENTION_DAYS, SLOTLOCK_CHANGE_TYPES, SLOTLOCK_MAX_WEBHOOK_ENDPOINTS, SLOTLOCK_WEBHOOK_FAILING_LIMIT_MS, slotlockChangeFeedHead, type SlotlockChange, type SlotlockChangeOrigin, type SlotlockChangeType, type SlotlockEventFeed, type SlotlockWebhookClaim, type SlotlockWebhookEndpoint, slotlockCoverageSourceId, } from './event-feed.js';
|
|
5
5
|
export { type SlotlockCloudEvent, type SlotlockWebhookPassResult, type SlotlockWebhookResponse, type SlotlockWebhookScheduler, type SlotlockWebhookSender, createSlotlockGuardedFetch, createSlotlockWebhookSender, deliverSlotlockWebhooks, isPrivateAddress, signSlotlockWebhook, slotlockCloudEvent, slotlockWebhookRetryDelayMs, startSlotlockWebhookDelivery, } from './webhooks.js';
|
|
6
6
|
export { type SlotlockMetrics, createSlotlockMetrics } from './metrics.js';
|
|
7
7
|
export { expandRules } from './rules.js';
|
|
@@ -11,11 +11,12 @@ export { emitVAvailability, parseVAvailability, recurrenceRuleFromRRule, SLOTLOC
|
|
|
11
11
|
export type { EmitVAvailabilityOptions, JSCalendarAlert, JSCalendarEvent, JSCalendarParticipant, JSCalendarRecurrenceRule, ParsedVAvailability, } from './interop.js';
|
|
12
12
|
export { createTimeZoneConverter, zonedCalendarDate, zonedDateTimeToUtc, zonedWallTime, } from './timezone.js';
|
|
13
13
|
export type { LocalDateTime, TimeZoneConverter, ZonedCalendarDate, ZonedWallTime, } from './timezone.js';
|
|
14
|
-
export { createSlotlockAgentServer, clientSupportsSlotlockMcpApp, invokeSlotlockAgentOperation, isSlotlockAgentOperation, slotlockAgentTools, slotlockApplicationOperations, slotlockOpenApi, slotlockCalendarResourceUri, slotlockMcpToolResult, parseSlotlockA2ATimestamp, resolveSlotlockAgentOperation, slotlockAgentOperationScope, SLOTLOCK_A2A_PROTOCOL_VERSION, SLOTLOCK_A2A_TASK_PAGE_SIZE, SLOTLOCK_A2A_TASK_STATES, SLOTLOCK_AGENT_OPERATION_LEGACY_NAMES, SLOTLOCK_AGENT_SERVER_VERSION, SLOTLOCK_MCP_LEGACY_PROTOCOL_VERSION, SLOTLOCK_MCP_PROTOCOL_VERSION, SLOTLOCK_MCP_RATE_LIMITED_ERROR_CODE, SLOTLOCK_MCP_RESOURCE_URI_TEMPLATE, SLOTLOCK_MCP_SUPPORTED_PROTOCOL_VERSIONS, SLOTLOCK_MCP_APP_HTML, SLOTLOCK_MCP_APP_MIME_TYPE, SLOTLOCK_MCP_APP_RESOURCE, SLOTLOCK_MCP_APP_RESOURCE_URI, SLOTLOCK_MCP_APPS_EXTENSION, SlotlockAgentOperationError, } from './agent-server.js';
|
|
14
|
+
export { createSlotlockAgentServer, clientSupportsSlotlockMcpApp, invokeSlotlockAgentOperation, isSlotlockAgentOperation, slotlockAgentTools, slotlockApplicationOperations, slotlockOpenApi, slotlockCalendarResourceUri, slotlockMcpToolResult, parseSlotlockA2ATimestamp, resolveSlotlockAgentOperation, slotlockAgentOperationScope, SLOTLOCK_A2A_PROTOCOL_VERSION, SLOTLOCK_A2A_TASK_PAGE_SIZE, SLOTLOCK_A2A_WATCH_SKILL, SLOTLOCK_A2A_TASK_STATES, SLOTLOCK_AGENT_OPERATION_LEGACY_NAMES, SLOTLOCK_AGENT_SERVER_VERSION, SLOTLOCK_MCP_LEGACY_PROTOCOL_VERSION, SLOTLOCK_MCP_PROTOCOL_VERSION, SLOTLOCK_MCP_RATE_LIMITED_ERROR_CODE, SLOTLOCK_MCP_RESOURCE_URI_TEMPLATE, SLOTLOCK_MCP_SUPPORTED_PROTOCOL_VERSIONS, SLOTLOCK_MCP_APP_HTML, SLOTLOCK_MCP_APP_MIME_TYPE, SLOTLOCK_MCP_APP_RESOURCE, SLOTLOCK_MCP_APP_RESOURCE_URI, SLOTLOCK_MCP_APPS_EXTENSION, SlotlockAgentOperationError, } from './agent-server.js';
|
|
15
15
|
export type { SlotlockAgentCalendarBackend, SlotlockAgentConfirmationOptions, SlotlockAgentErrorContext, SlotlockAgentInvocationContext, SlotlockAgentOperation, SlotlockAgentOperationDispatchOptions, SlotlockAgentOperationDispatchResult, SlotlockAgentPrincipal, SlotlockAgentScope, SlotlockAgentServerEvent, SlotlockAgentServerOAuthOptions, SlotlockAgentServerOptions, SlotlockAgentSubscriptionOptions, SlotlockAgentWriteOperation, SlotlockMcpToolResult, SlotlockTraceContext, } from './agent-server.js';
|
|
16
16
|
export { createSlotlockNodeServer } from './node-server.js';
|
|
17
17
|
export type { SlotlockNodeListenOptions, SlotlockNodeServer, SlotlockNodeServerAddress, SlotlockNodeServerCloseResult, SlotlockNodeServerErrorPhase, SlotlockNodeServerFetchTarget, SlotlockNodeServerOptions, } from './node-server.js';
|
|
18
|
-
export { createSlotlockStoreAgentBackend, createSlotlockStoreApplicationBackend, projectSlotlockCalendarEvent, } from './agent-store-backend.js';
|
|
18
|
+
export { createSlotlockStoreAgentBackend, createSlotlockStoreApplicationBackend, createSlotlockStoreChangeSource, projectSlotlockCalendarEvent, } from './agent-store-backend.js';
|
|
19
|
+
export { SLOTLOCK_FEED_BUSY_MS, SLOTLOCK_FEED_IDLE_MS, type SlotlockAgentChange, type SlotlockAgentChangeSource, } from './change-watch.js';
|
|
19
20
|
export type { SlotlockStoreAgentBackendOptions, SlotlockStoreApplicationBackendOptions, } from './agent-store-backend.js';
|
|
20
21
|
export { SLOTLOCK_APPLICATION_OPERATIONS, SLOTLOCK_APPLICATION_SCOPE, isSlotlockApplicationOperation, } from './application-api.js';
|
|
21
22
|
export type { SlotlockApplicationBackend, SlotlockApplicationInvocationContext, SlotlockApplicationOperation, SlotlockApplicationOperationDefinition, } from './application-api.js';
|
|
@@ -26,11 +27,11 @@ export { createSlotlockDashboard, createSlotlockDashboardResources, createSlotlo
|
|
|
26
27
|
export type { SlotlockDashboard, SlotlockDashboardAssistantOptions, SlotlockDashboardOptions, SlotlockDashboardResources, SlotlockDashboardState, } from './dashboard.js';
|
|
27
28
|
export { createSlotlockAssistantStore, createSlotlockMemoryAssistantStore, SLOTLOCK_ASSISTANT_TOOL_USE, } from './assistant.js';
|
|
28
29
|
export type { SlotlockAssistantStore } from './assistant.js';
|
|
29
|
-
export type { CreateSlotlockApiKeyInput, SlotlockApiKey, SlotlockApiKeyAuthenticatorOptions, SlotlockApiKeyIdentity, SlotlockApiKeyScope, SlotlockApiKeyStore, } from './api-keys.js';
|
|
30
|
+
export type { CreateSlotlockApiKeyInput, SlotlockApiKey, SlotlockApiKeyAuthenticatorOptions, SlotlockApiKeyChange, SlotlockApiKeyIdentity, SlotlockApiKeyScope, SlotlockApiKeyStore, } from './api-keys.js';
|
|
30
31
|
export { ICalendarWorkerBusyError, ICalendarWorkerPoolClosedError, createICalendarWorkerPool, } from './ical-worker.js';
|
|
31
32
|
export type { ICalendarWorkerPool, ICalendarWorkerPoolOptions } from './ical-worker.js';
|
|
32
33
|
export { CalendarContractError, CalendarExpansionLimitError, emitICalendar, emitITipCalendar, expandCalendarEventOccurrences, normalizeCalendarEventContent, normalizeExternalCalendarChange, parseICalendarChanges, parseICalendarFeed, parseTrustedICalendarEvents, recurrenceRuleProblem, storedCalendarEventProblem, } from './sync.js';
|
|
33
34
|
export type { ExternalCalendarChange, ExternalCalendarChangeInput, ExternalCalendarStatus, ICalendarEvent, ICalendarExpansionWindow, ICalendarFeedRead, ICalendarSeriesRefusal, ITipMethod, } from './sync.js';
|
|
34
35
|
export { createSlotlockApplicationRoleGrantsDdl, createSlotlockTenantRlsDdl, SLOTLOCK_API_KEY_FUNCTIONS, SLOTLOCK_API_KEY_TABLES, SLOTLOCK_CORE_DDL, SLOTLOCK_DASHBOARD_FUNCTIONS, SLOTLOCK_DASHBOARD_TABLES, SLOTLOCK_DEFINER_FUNCTIONS, SLOTLOCK_DEFINER_TABLES, SLOTLOCK_OPERATOR_FUNCTIONS, SLOTLOCK_SCHEMA_FUNCTIONS, SLOTLOCK_SCHEMA_TABLES, SLOTLOCK_SCHEMA_VERSION, SLOTLOCK_TENANT_ADMIN_FUNCTIONS, SLOTLOCK_TENANT_ADMIN_TABLES, SLOTLOCK_TENANT_CONTEXT_SETTING, SLOTLOCK_TENANT_RLS_DDL, SLOTLOCK_TENANT_TABLES, } from './ddl.js';
|
|
35
36
|
export { assertSlotlockSchemaCurrent, calendarEventRollingHorizon, createSlotlockStore, readSlotlockSchemaVersion, SLOTLOCK_AGENT_OWNER_COMMAND_QUOTA, SLOTLOCK_AGENT_OWNER_EVENT_QUOTA, SLOTLOCK_CALENDAR_HORIZON_DAYS, SLOTLOCK_EVENT_COMMAND_RETENTION_DAYS, SLOTLOCK_INTERNAL_EVENT_OWNER_REF, SLOTLOCK_LOCAL_RECURRENCE_COVERAGE_SOURCE, SLOTLOCK_MATERIALIZATION_WINDOW_DAYS, SLOTLOCK_MAX_EVENT_DURATION_DAYS, SLOTLOCK_TENANT_WRITE_METHODS, type SlotlockHoldTenantOptions, type SlotlockMigrationOptions, type SlotlockSql, type SlotlockStore, type SlotlockStoreOptions, type SlotlockTenantLimits, type SlotlockTenantStore, } from './store.js';
|
|
36
|
-
export type { AcquireHoldResult, BookingRetentionResult, CalendarAttendee, CalendarAttendeeRole, CalendarCoverageAssessment, CalendarCoverageCommand, CalendarCoverageResult, CalendarEventContent, CalendarEventOccurrence, CalendarEventRetentionResult, CalendarEventStatus, CalendarFreeBusy, CalendarHorizonRollCursor, CalendarHorizonRollResult, CalendarOrganizer, CalendarParticipationStatus, CalendarRecurrence, CalendarRecurrenceException, CalendarReminder, CalendarTransparency, ConfirmHoldResult, CancelCalendarEventResult, CancelExternalReservationResult, CreateReservationResult, DeleteResourceResult, ExternalReservationCommand, ExternalReservationResult, Interval, SlotlockAgendaEntry, SlotlockHold, SlotlockCalendarEvent, SlotlockReservation, SlotlockResource, SlotlockTenantErasure, PutCalendarEventCommand, PutCalendarEventResult, ReleaseHoldResult, TrustedICalendarEvent, WeeklyAvailabilityRule, } from './types.js';
|
|
37
|
+
export type { AcquireHoldResult, BookingRetentionResult, CalendarAttendee, CalendarAttendeeRole, CalendarCoverageAssessment, CalendarCoverageCommand, CalendarCoverageResult, CalendarEventContent, CalendarEventOccurrence, CalendarEventRetentionResult, CalendarEventStatus, CalendarFreeBusy, CalendarHorizonRollCursor, CalendarHorizonRollResult, CalendarOrganizer, CalendarParticipationStatus, CalendarRecurrence, CalendarRecurrenceException, CalendarReminder, CalendarTransparency, ConfirmHoldResult, CancelCalendarEventResult, CancelExternalReservationResult, CreateReservationResult, DeleteResourceResult, ExternalReservationCommand, ExternalReservationResult, Interval, SlotlockAgendaEntry, SlotlockHold, SlotlockCalendarEvent, SlotlockReservation, SlotlockResource, SlotlockTenantErasure, SlotlockBookingRuleDefaults, PutCalendarEventCommand, PutCalendarEventResult, ReleaseHoldResult, TrustedICalendarEvent, WeeklyAvailabilityRule, } from './types.js';
|
package/index.js
CHANGED
|
@@ -4,17 +4,18 @@
|
|
|
4
4
|
// TypeScript 6 default) still loads @types/node.
|
|
5
5
|
/// <reference types="node" preserve="true" />
|
|
6
6
|
export { findNextAvailable, mergeIntervals, subtractBusy } from './engine.js';
|
|
7
|
-
export { bookingRuleBroken, bookingSearch, nextStepStart, parseBookingRules, } from './booking-rules.js';
|
|
8
|
-
export { SLOTLOCK_CHANGE_RETENTION_DAYS, SLOTLOCK_CHANGE_TYPES, SLOTLOCK_MAX_WEBHOOK_ENDPOINTS, SLOTLOCK_WEBHOOK_FAILING_LIMIT_MS, } from './event-feed.js';
|
|
7
|
+
export { bookingRuleBroken, bookingSearch, bookingSearchBlocked, bookingSearchReach, nextStepStart, parseBookingRuleDefaults, parseBookingRules, } from './booking-rules.js';
|
|
8
|
+
export { SLOTLOCK_CHANGE_ORIGINS, SLOTLOCK_CHANGE_RETENTION_DAYS, SLOTLOCK_CHANGE_TYPES, SLOTLOCK_MAX_WEBHOOK_ENDPOINTS, SLOTLOCK_WEBHOOK_FAILING_LIMIT_MS, slotlockChangeFeedHead, slotlockCoverageSourceId, } from './event-feed.js';
|
|
9
9
|
export { createSlotlockGuardedFetch, createSlotlockWebhookSender, deliverSlotlockWebhooks, isPrivateAddress, signSlotlockWebhook, slotlockCloudEvent, slotlockWebhookRetryDelayMs, startSlotlockWebhookDelivery, } from './webhooks.js';
|
|
10
10
|
export { createSlotlockMetrics } from './metrics.js';
|
|
11
11
|
export { expandRules } from './rules.js';
|
|
12
12
|
export { availabilityRuleProblem, parseAvailabilityRules, rulesToWeeklyHours, SLOTLOCK_MAX_AVAILABILITY_RULES, weeklyHoursToRules, } from './weekly-hours.js';
|
|
13
13
|
export { emitVAvailability, parseVAvailability, recurrenceRuleFromRRule, SLOTLOCK_MAX_VAVAILABILITY_CHARACTERS, toJSCalendarEvent, } from './interop.js';
|
|
14
14
|
export { createTimeZoneConverter, zonedCalendarDate, zonedDateTimeToUtc, zonedWallTime, } from './timezone.js';
|
|
15
|
-
export { createSlotlockAgentServer, clientSupportsSlotlockMcpApp, invokeSlotlockAgentOperation, isSlotlockAgentOperation, slotlockAgentTools, slotlockApplicationOperations, slotlockOpenApi, slotlockCalendarResourceUri, slotlockMcpToolResult, parseSlotlockA2ATimestamp, resolveSlotlockAgentOperation, slotlockAgentOperationScope, SLOTLOCK_A2A_PROTOCOL_VERSION, SLOTLOCK_A2A_TASK_PAGE_SIZE, SLOTLOCK_A2A_TASK_STATES, SLOTLOCK_AGENT_OPERATION_LEGACY_NAMES, SLOTLOCK_AGENT_SERVER_VERSION, SLOTLOCK_MCP_LEGACY_PROTOCOL_VERSION, SLOTLOCK_MCP_PROTOCOL_VERSION, SLOTLOCK_MCP_RATE_LIMITED_ERROR_CODE, SLOTLOCK_MCP_RESOURCE_URI_TEMPLATE, SLOTLOCK_MCP_SUPPORTED_PROTOCOL_VERSIONS, SLOTLOCK_MCP_APP_HTML, SLOTLOCK_MCP_APP_MIME_TYPE, SLOTLOCK_MCP_APP_RESOURCE, SLOTLOCK_MCP_APP_RESOURCE_URI, SLOTLOCK_MCP_APPS_EXTENSION, SlotlockAgentOperationError, } from './agent-server.js';
|
|
15
|
+
export { createSlotlockAgentServer, clientSupportsSlotlockMcpApp, invokeSlotlockAgentOperation, isSlotlockAgentOperation, slotlockAgentTools, slotlockApplicationOperations, slotlockOpenApi, slotlockCalendarResourceUri, slotlockMcpToolResult, parseSlotlockA2ATimestamp, resolveSlotlockAgentOperation, slotlockAgentOperationScope, SLOTLOCK_A2A_PROTOCOL_VERSION, SLOTLOCK_A2A_TASK_PAGE_SIZE, SLOTLOCK_A2A_WATCH_SKILL, SLOTLOCK_A2A_TASK_STATES, SLOTLOCK_AGENT_OPERATION_LEGACY_NAMES, SLOTLOCK_AGENT_SERVER_VERSION, SLOTLOCK_MCP_LEGACY_PROTOCOL_VERSION, SLOTLOCK_MCP_PROTOCOL_VERSION, SLOTLOCK_MCP_RATE_LIMITED_ERROR_CODE, SLOTLOCK_MCP_RESOURCE_URI_TEMPLATE, SLOTLOCK_MCP_SUPPORTED_PROTOCOL_VERSIONS, SLOTLOCK_MCP_APP_HTML, SLOTLOCK_MCP_APP_MIME_TYPE, SLOTLOCK_MCP_APP_RESOURCE, SLOTLOCK_MCP_APP_RESOURCE_URI, SLOTLOCK_MCP_APPS_EXTENSION, SlotlockAgentOperationError, } from './agent-server.js';
|
|
16
16
|
export { createSlotlockNodeServer } from './node-server.js';
|
|
17
|
-
export { createSlotlockStoreAgentBackend, createSlotlockStoreApplicationBackend, projectSlotlockCalendarEvent, } from './agent-store-backend.js';
|
|
17
|
+
export { createSlotlockStoreAgentBackend, createSlotlockStoreApplicationBackend, createSlotlockStoreChangeSource, projectSlotlockCalendarEvent, } from './agent-store-backend.js';
|
|
18
|
+
export { SLOTLOCK_FEED_BUSY_MS, SLOTLOCK_FEED_IDLE_MS, } from './change-watch.js';
|
|
18
19
|
export { SLOTLOCK_APPLICATION_OPERATIONS, SLOTLOCK_APPLICATION_SCOPE, isSlotlockApplicationOperation, } from './application-api.js';
|
|
19
20
|
export { maintainSlotlockTenant, startSlotlockMaintenance, withSlotlockMaintenanceLock, } from './maintenance.js';
|
|
20
21
|
export { createSlotlockApiKeyAuthenticator, createSlotlockApiKeyStore, generateSlotlockApiKey, isSlotlockApiKey, slotlockApiKeyDigest, SLOTLOCK_API_KEY_ACTIVE_LIMIT, SLOTLOCK_API_KEY_MAX_LIFETIME_DAYS, SLOTLOCK_API_KEY_PREFIX, SLOTLOCK_API_KEY_RETAINED_LIMIT, SLOTLOCK_API_KEY_SCOPES, } from './api-keys.js';
|
package/maintenance.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ignoreRejection } from './hooks.js';
|
|
2
|
-
import { SLOTLOCK_MAX_WAITLIST_PER_TENANT, calendarEventRollingHorizon } from './store.js';
|
|
2
|
+
import { SLOTLOCK_MAX_WAITLIST_PER_TENANT, calendarEventRollingHorizon, } from './store.js';
|
|
3
3
|
/**
|
|
4
4
|
* One maintenance pass for a tenant: roll its recurring events to the rolling horizon, then purge
|
|
5
5
|
* its expired holds with their answers and prune its events' and bookings' bookkeeping past the
|
|
@@ -11,6 +11,8 @@ export async function maintainSlotlockTenant(store, tenantRef, options = {}) {
|
|
|
11
11
|
if (!(Number.isSafeInteger(batches) && batches >= 1 && batches <= 100)) {
|
|
12
12
|
throw new RangeError('batches must be a whole number from 1 to 100');
|
|
13
13
|
}
|
|
14
|
+
// What a pass changes (a no-show released, a waitlist offer) is recorded as maintenance's.
|
|
15
|
+
const withTenant = (callback) => store.withTenant(tenantRef, callback, { origin: 'maintenance' });
|
|
14
16
|
const window = calendarEventRollingHorizon();
|
|
15
17
|
let extended = 0;
|
|
16
18
|
let conflicts = 0;
|
|
@@ -24,7 +26,7 @@ export async function maintainSlotlockTenant(store, tenantRef, options = {}) {
|
|
|
24
26
|
for (let batch = 0; batch < batches; batch++) {
|
|
25
27
|
options.signal?.throwIfAborted();
|
|
26
28
|
const from = after;
|
|
27
|
-
const roll = await
|
|
29
|
+
const roll = await withTenant((tenant) => tenant.rollCalendarEventHorizon({ tenantRef, window, ...(from ? { after: from } : {}) }));
|
|
28
30
|
extended += roll.extended;
|
|
29
31
|
conflicts += roll.conflicts;
|
|
30
32
|
refused += roll.refused;
|
|
@@ -47,17 +49,17 @@ export async function maintainSlotlockTenant(store, tenantRef, options = {}) {
|
|
|
47
49
|
for (let batch = 0; batch < batches && (eventsLeft || bookingsLeft || changesLeft); batch++) {
|
|
48
50
|
options.signal?.throwIfAborted();
|
|
49
51
|
if (eventsLeft) {
|
|
50
|
-
const prune = await
|
|
52
|
+
const prune = await withTenant((tenant) => tenant.pruneCalendarEventRetention({ tenantRef }));
|
|
51
53
|
pruned += prune.commandsDeleted + prune.tombstonesDeleted;
|
|
52
54
|
eventsLeft = prune.hasMore;
|
|
53
55
|
}
|
|
54
56
|
if (bookingsLeft) {
|
|
55
|
-
const prune = await
|
|
57
|
+
const prune = await withTenant((tenant) => tenant.pruneBookingRetention({ tenantRef }));
|
|
56
58
|
pruned += prune.expiredHoldsDeleted + prune.holdCommandsDeleted + prune.tombstonesDeleted;
|
|
57
59
|
bookingsLeft = prune.hasMore;
|
|
58
60
|
}
|
|
59
61
|
if (changesLeft) {
|
|
60
|
-
const deleted = await
|
|
62
|
+
const deleted = await withTenant((tenant) => tenant.pruneChanges({ tenantRef, limit: CHANGE_PRUNE_BATCH }));
|
|
61
63
|
pruned += deleted;
|
|
62
64
|
changesLeft = deleted === CHANGE_PRUNE_BATCH;
|
|
63
65
|
}
|
|
@@ -65,11 +67,11 @@ export async function maintainSlotlockTenant(store, tenantRef, options = {}) {
|
|
|
65
67
|
options.signal?.throwIfAborted();
|
|
66
68
|
// Conversations last 2 hours after their last message (dashboard.ts): an expired one is
|
|
67
69
|
// unreadable already, and deleted here.
|
|
68
|
-
pruned += await
|
|
69
|
-
const zones = await
|
|
70
|
-
const staleSources = await
|
|
70
|
+
pruned += await withTenant((tenant) => tenant.pruneAssistantConversations({ tenantRef }));
|
|
71
|
+
const zones = await withTenant((tenant) => tenant.followZoneRules({ tenantRef, move: options.followZoneRules === true }));
|
|
72
|
+
const staleSources = await withTenant((tenant) => tenant.reportStaleCoverage({ tenantRef }));
|
|
71
73
|
// No-shows first: the time they free is offered to the waitlist in the same pass.
|
|
72
|
-
const noShows = await
|
|
74
|
+
const noShows = await withTenant((tenant) => tenant.releaseNoShows({ tenantRef }));
|
|
73
75
|
// The whole waitlist, in join order, a page at a time and each page its own transaction, so a
|
|
74
76
|
// later entry whose window is free is offered however many wait before it.
|
|
75
77
|
let waitlistOffers = 0;
|
|
@@ -77,7 +79,7 @@ export async function maintainSlotlockTenant(store, tenantRef, options = {}) {
|
|
|
77
79
|
for (let pages = 0; pages < WAITLIST_PAGES; pages++) {
|
|
78
80
|
options.signal?.throwIfAborted();
|
|
79
81
|
const from = page;
|
|
80
|
-
const offers = await
|
|
82
|
+
const offers = await withTenant((tenant) => tenant.offerWaitlist({ tenantRef, ...(from ? { after: from } : {}) }));
|
|
81
83
|
waitlistOffers += offers.offered;
|
|
82
84
|
page = offers.next;
|
|
83
85
|
if (!page)
|