slotlock 0.9.0 → 0.10.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.
Files changed (61) hide show
  1. package/CHANGELOG.md +305 -0
  2. package/NOTICE +2 -0
  3. package/README.md +4 -2
  4. package/agent-store-backend.js +209 -15
  5. package/application-api.d.ts +10 -1
  6. package/application-api.js +213 -2
  7. package/availability-store.js +13 -5
  8. package/booking-rules.d.ts +11 -0
  9. package/booking-rules.js +21 -1
  10. package/config.d.ts +50 -0
  11. package/config.js +107 -0
  12. package/dashboard-scope.d.ts +7 -0
  13. package/dashboard-scope.js +21 -0
  14. package/dashboard.d.ts +97 -2
  15. package/dashboard.js +3068 -284
  16. package/ddl-identity.d.ts +25 -0
  17. package/ddl-identity.js +881 -0
  18. package/ddl-scim.d.ts +5 -0
  19. package/ddl-scim.js +740 -0
  20. package/ddl-sso.d.ts +15 -0
  21. package/ddl-sso.js +1033 -0
  22. package/ddl.d.ts +40 -9
  23. package/ddl.js +2123 -49
  24. package/domains.d.ts +117 -0
  25. package/domains.js +340 -0
  26. package/event-feed.d.ts +1 -1
  27. package/event-feed.js +6 -34
  28. package/holiday-import.d.ts +1 -1
  29. package/holiday-import.js +2 -3
  30. package/holidays.js +70 -11
  31. package/index.d.ts +12 -4
  32. package/index.js +8 -2
  33. package/invitation-email.d.ts +26 -0
  34. package/invitation-email.js +73 -0
  35. package/maintenance.d.ts +7 -2
  36. package/maintenance.js +15 -1
  37. package/oidc.d.ts +135 -0
  38. package/oidc.js +452 -0
  39. package/openapi.json +1886 -6
  40. package/package.json +2 -1
  41. package/scim-server.d.ts +19 -0
  42. package/scim-server.js +243 -0
  43. package/scim.d.ts +180 -0
  44. package/scim.js +416 -0
  45. package/self-host.d.ts +19 -6
  46. package/self-host.js +262 -7
  47. package/slotlock.manifest.json +1 -1
  48. package/store.d.ts +273 -2
  49. package/store.js +984 -72
  50. package/types.d.ts +33 -1
  51. package/types.js +68 -1
  52. package/workspace-domains.d.ts +146 -0
  53. package/workspace-domains.js +249 -0
  54. package/workspace-roles.d.ts +47 -0
  55. package/workspace-roles.js +304 -0
  56. package/workspace-scim.d.ts +89 -0
  57. package/workspace-scim.js +157 -0
  58. package/workspace-sso.d.ts +131 -0
  59. package/workspace-sso.js +180 -0
  60. package/workspaces.d.ts +240 -0
  61. package/workspaces.js +433 -0
package/index.d.ts CHANGED
@@ -36,7 +36,7 @@ export { maintainSlotlockTenant, startSlotlockMaintenance, withSlotlockMaintenan
36
36
  export type { SlotlockMaintenanceResult, SlotlockMaintenanceScheduler, SlotlockMaintenanceSchedulerOptions, } from './maintenance.js';
37
37
  export { createSlotlockApiKeyAuthenticator, createSlotlockApiKeyStore, generateSlotlockApiKey, isSlotlockApiKey, isSlotlockTestTenant, slotlockLiveTenant, SLOTLOCK_TEST_API_KEY_PREFIX, SLOTLOCK_TEST_TENANT_SUFFIX, 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';
38
38
  export { createSlotlockDashboard, createSlotlockDashboardResources, createSlotlockAcceptanceRecords, createSlotlockDashboardState, createSlotlockMemoryDashboardState, SLOTLOCK_DASHBOARD_MAX_RESOURCES, } from './dashboard.js';
39
- export type { SlotlockDashboard, SlotlockDashboardAssistantOptions, SlotlockDashboardOptions, SlotlockDashboardResources, SlotlockDashboardState, } from './dashboard.js';
39
+ export type { SlotlockDashboard, SlotlockDashboardAssistantOptions, SlotlockDashboardOptions, SlotlockDashboardResources, SlotlockDashboardState, SlotlockInvitationSender, } from './dashboard.js';
40
40
  export { createSlotlockAssistantStore, createSlotlockMemoryAssistantStore, SLOTLOCK_ASSISTANT_TOOL_USE, } from './assistant.js';
41
41
  export type { SlotlockAssistantStore } from './assistant.js';
42
42
  export { createSlotlockPartnerKeyAuthenticator, createSlotlockPartnerStore, parseSlotlockPartnerContract, slotlockPartnerTenantRef, SLOTLOCK_PARTNER_ACCOUNT, SLOTLOCK_PARTNER_ID, SLOTLOCK_PARTNER_SCOPE, } from './partners.js';
@@ -46,6 +46,13 @@ export type { SlotlockOverageReport } from './overage.js';
46
46
  export { createSlotlockOtlpExporter, readSlotlockOtlpConfig, slotlockCallSpan } from './otel.js';
47
47
  export type { SlotlockOtlpConfig, SlotlockTraceExporter } from './otel.js';
48
48
  export type { SlotlockDashboardTeams } from './teams.js';
49
+ export { createSlotlockInvitationEmailSender, SlotlockInvitationEmailError, slotlockInvitationEmail, } from './invitation-email.js';
50
+ export type { SlotlockInvitationEmailConfig } from './invitation-email.js';
51
+ export { createSlotlockDashboardWorkspaces, createSlotlockMemoryDashboardWorkspaces, createSlotlockWorkspaceCheck, } from './workspaces.js';
52
+ export type { SlotlockDashboardWorkspaces, SlotlockGitHubMembership, SlotlockInvitationPreview, SlotlockTeamRefusal, SlotlockTeamWorkspace, SlotlockWorkspaceInvitation, SlotlockWorkspaceMember, SlotlockWorkspaceStanding, SlotlockWorkspaceTeams, SlotlockDashboardSession, SlotlockDashboardSessions, } from './workspaces.js';
53
+ export { SLOTLOCK_DASHBOARD_PAGE_CAPABILITIES, SLOTLOCK_DASHBOARD_POST_CAPABILITIES, SLOTLOCK_FRESH_CAPABILITIES, SLOTLOCK_ROLE_CAPABILITIES, SLOTLOCK_SCOPED_CAPABILITIES, SLOTLOCK_SCOPED_DASHBOARD_PAGES, SLOTLOCK_SCOPED_DASHBOARD_POSTS, SLOTLOCK_WORKSPACE_CAPABILITIES, SLOTLOCK_WORKSPACE_ROLE_NAMES, SLOTLOCK_WORKSPACE_ROLES, isSlotlockWorkspaceRole, slotlockMemberCan, slotlockRoleCan, slotlockRoleKeyScopes, slotlockRoleMayGrant, } from './workspace-roles.js';
54
+ export { slotlockDashboardRequestScope } from './dashboard-scope.js';
55
+ export type { SlotlockWorkspaceCapability, SlotlockWorkspaceRole } from './workspace-roles.js';
49
56
  export { closeSlotlockPartnerMonth, priceSlotlockPartnerMonth, slotlockClientLabel, slotlockPartnerMonthChecksum, } from './partner-billing.js';
50
57
  export type { SlotlockPartnerAccountMonth, SlotlockPartnerInvoiceLine, SlotlockPartnerMonthClose, } from './partner-billing.js';
51
58
  export type { SlotlockPartnerAccount, SlotlockPartnerContract, SlotlockPartnerPlan, SlotlockPartnerPricing, SlotlockPartnerStore, } from './partners.js';
@@ -54,6 +61,7 @@ export { ICalendarWorkerBusyError, ICalendarWorkerPoolClosedError, createICalend
54
61
  export type { ICalendarWorkerPool, ICalendarWorkerPoolOptions } from './ical-worker.js';
55
62
  export { CalendarContractError, CalendarExpansionLimitError, emitICalendar, emitITipCalendar, expandCalendarEventOccurrences, normalizeCalendarEventContent, normalizeExternalCalendarChange, parseICalendarChanges, parseICalendarFeed, parseTrustedICalendarEvents, recurrenceRuleProblem, storedCalendarEventProblem, } from './sync.js';
56
63
  export type { ExternalCalendarChange, ExternalCalendarChangeInput, ExternalCalendarStatus, ICalendarEvent, ICalendarExpansionWindow, ICalendarFeedRead, ICalendarSeriesRefusal, ITipMethod, } from './sync.js';
57
- 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';
58
- export { assertSlotlockSchemaCurrent, calendarEventRollingHorizon, createSlotlockStore, readSlotlockSchemaVersion, SLOTLOCK_AGENT_OWNER_COMMAND_QUOTA, SLOTLOCK_AGENT_OWNER_EVENT_QUOTA, SLOTLOCK_CALENDAR_HORIZON_DAYS, SLOTLOCK_SOURCE_BUSY_MAX, 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_MAX_CONTACTS_PER_TENANT, SLOTLOCK_PAYMENT_HOLD_MAX_TTL_MS, SLOTLOCK_TENANT_WRITE_METHODS, type SlotlockHoldTenantOptions, type SlotlockMigrationOptions, type SlotlockSql, type SlotlockStore, type SlotlockStoreOptions, type SlotlockTenantLimits, type SlotlockTenantStore, } from './store.js';
59
- 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, MarkHoldPaidResult, SlotlockContact, SlotlockContactConsent, PutContactResult, SourceBusyCommand, SourceBusyResult, SlotlockCalendarEvent, SlotlockBookingDescription, SlotlockReservation, SlotlockResource, SlotlockTenantErasure, SlotlockBookingRuleDefaults, PutCalendarEventCommand, PutCalendarEventResult, ReleaseHoldResult, TrustedICalendarEvent, SlotlockInheritedAvailability, WeeklyAvailabilityRule, } from './types.js';
64
+ export { createSlotlockApplicationRoleGrantsDdl, createSlotlockTenantRlsDdl, SLOTLOCK_API_KEY_FUNCTIONS, SLOTLOCK_API_KEY_TABLES, SLOTLOCK_CORE_DDL, SLOTLOCK_DASHBOARD_FUNCTIONS, SLOTLOCK_DASHBOARD_TABLES, SLOTLOCK_APPEND_ONLY_TABLES, SLOTLOCK_AUDIT_RETENTION_DAYS, SLOTLOCK_CHANGE_RETENTION_BY_PLAN, 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';
65
+ export { assertSlotlockSchemaCurrent, calendarEventRollingHorizon, createSlotlockStore, readSlotlockSchemaVersion, SLOTLOCK_AGENT_OWNER_COMMAND_QUOTA, SLOTLOCK_AGENT_OWNER_EVENT_QUOTA, SLOTLOCK_CALENDAR_HORIZON_DAYS, SLOTLOCK_SOURCE_BUSY_MAX, 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_MAX_CONTACTS_PER_TENANT, SLOTLOCK_PAYMENT_HOLD_MAX_TTL_MS, SLOTLOCK_TENANT_WRITE_METHODS, SLOTLOCK_TENANT_EXPORT_HOURS, SLOTLOCK_TENANT_EXPORT_MAX_BYTES, SLOTLOCK_TENANT_EXPORT_PART_ROWS, SLOTLOCK_MAX_TENANT_EXPORTS, buildSlotlockTenantExport, slotlockAuditEventRecord, type SlotlockAuditEvent, type SlotlockAuditEventInput, type SlotlockSubject, type SlotlockSubjectErasure, type SlotlockSubjectExport, type SlotlockTenantExport, type SlotlockTenantExportPartResult, type SlotlockHoldTenantOptions, type SlotlockMigrationOptions, type SlotlockSql, type SlotlockStore, type SlotlockStoreOptions, type SlotlockTenantLimits, type SlotlockTenantStore, } from './store.js';
66
+ 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, MarkHoldPaidResult, SlotlockContact, SlotlockContactConsent, PutContactResult, SourceBusyCommand, SourceBusyResult, SlotlockCalendarEvent, SlotlockBookingDescription, SlotlockReservation, SlotlockResource, SlotlockTenantErasure, SlotlockBookingRuleDefaults, PutCalendarEventCommand, PutCalendarEventResult, ReleaseHoldResult, TrustedICalendarEvent, SlotlockInheritedAvailability, WeeklyAvailabilityRule, SlotlockSubjectTable, SlotlockTenantExportTable, } from './types.js';
67
+ export { SLOTLOCK_SUBJECT_TABLES, SLOTLOCK_TENANT_EXPORT_LEFT_OUT, SLOTLOCK_TENANT_EXPORT_TABLES, } from './types.js';
package/index.js CHANGED
@@ -34,8 +34,14 @@ export { createSlotlockPartnerKeyAuthenticator, createSlotlockPartnerStore, pars
34
34
  export { createSlotlockDashboardTeams, createSlotlockOrganizationCheck } from './teams.js';
35
35
  export { reportSlotlockOverage, SLOTLOCK_DEFAULT_OVERAGE_STOP_PERCENT } from './overage.js';
36
36
  export { createSlotlockOtlpExporter, readSlotlockOtlpConfig, slotlockCallSpan } from './otel.js';
37
+ export { createSlotlockInvitationEmailSender, SlotlockInvitationEmailError, slotlockInvitationEmail, } from './invitation-email.js';
38
+ export { createSlotlockDashboardWorkspaces, createSlotlockMemoryDashboardWorkspaces, createSlotlockWorkspaceCheck, } from './workspaces.js';
39
+ export { SLOTLOCK_DASHBOARD_PAGE_CAPABILITIES, SLOTLOCK_DASHBOARD_POST_CAPABILITIES, SLOTLOCK_FRESH_CAPABILITIES, SLOTLOCK_ROLE_CAPABILITIES, SLOTLOCK_SCOPED_CAPABILITIES, SLOTLOCK_SCOPED_DASHBOARD_PAGES, SLOTLOCK_SCOPED_DASHBOARD_POSTS, SLOTLOCK_WORKSPACE_CAPABILITIES, SLOTLOCK_WORKSPACE_ROLE_NAMES, SLOTLOCK_WORKSPACE_ROLES, isSlotlockWorkspaceRole, slotlockMemberCan, slotlockRoleCan, slotlockRoleKeyScopes, slotlockRoleMayGrant, } from './workspace-roles.js';
40
+ // For resource adapters of one's own that keep to a limited member's scope (`keepsToScope`).
41
+ export { slotlockDashboardRequestScope } from './dashboard-scope.js';
37
42
  export { closeSlotlockPartnerMonth, priceSlotlockPartnerMonth, slotlockClientLabel, slotlockPartnerMonthChecksum, } from './partner-billing.js';
38
43
  export { ICalendarWorkerBusyError, ICalendarWorkerPoolClosedError, createICalendarWorkerPool, } from './ical-worker.js';
39
44
  export { CalendarContractError, CalendarExpansionLimitError, emitICalendar, emitITipCalendar, expandCalendarEventOccurrences, normalizeCalendarEventContent, normalizeExternalCalendarChange, parseICalendarChanges, parseICalendarFeed, parseTrustedICalendarEvents, recurrenceRuleProblem, storedCalendarEventProblem, } from './sync.js';
40
- 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';
41
- export { assertSlotlockSchemaCurrent, calendarEventRollingHorizon, createSlotlockStore, readSlotlockSchemaVersion, SLOTLOCK_AGENT_OWNER_COMMAND_QUOTA, SLOTLOCK_AGENT_OWNER_EVENT_QUOTA, SLOTLOCK_CALENDAR_HORIZON_DAYS, SLOTLOCK_SOURCE_BUSY_MAX, 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_MAX_CONTACTS_PER_TENANT, SLOTLOCK_PAYMENT_HOLD_MAX_TTL_MS, SLOTLOCK_TENANT_WRITE_METHODS, } from './store.js';
45
+ export { createSlotlockApplicationRoleGrantsDdl, createSlotlockTenantRlsDdl, SLOTLOCK_API_KEY_FUNCTIONS, SLOTLOCK_API_KEY_TABLES, SLOTLOCK_CORE_DDL, SLOTLOCK_DASHBOARD_FUNCTIONS, SLOTLOCK_DASHBOARD_TABLES, SLOTLOCK_APPEND_ONLY_TABLES, SLOTLOCK_AUDIT_RETENTION_DAYS, SLOTLOCK_CHANGE_RETENTION_BY_PLAN, 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';
46
+ export { assertSlotlockSchemaCurrent, calendarEventRollingHorizon, createSlotlockStore, readSlotlockSchemaVersion, SLOTLOCK_AGENT_OWNER_COMMAND_QUOTA, SLOTLOCK_AGENT_OWNER_EVENT_QUOTA, SLOTLOCK_CALENDAR_HORIZON_DAYS, SLOTLOCK_SOURCE_BUSY_MAX, 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_MAX_CONTACTS_PER_TENANT, SLOTLOCK_PAYMENT_HOLD_MAX_TTL_MS, SLOTLOCK_TENANT_WRITE_METHODS, SLOTLOCK_TENANT_EXPORT_HOURS, SLOTLOCK_TENANT_EXPORT_MAX_BYTES, SLOTLOCK_TENANT_EXPORT_PART_ROWS, SLOTLOCK_MAX_TENANT_EXPORTS, buildSlotlockTenantExport, slotlockAuditEventRecord, } from './store.js';
47
+ export { SLOTLOCK_SUBJECT_TABLES, SLOTLOCK_TENANT_EXPORT_LEFT_OUT, SLOTLOCK_TENANT_EXPORT_TABLES, } from './types.js';
@@ -0,0 +1,26 @@
1
+ import type { SlotlockInvitationSender } from './dashboard.js';
2
+ /** Where invitations are sent from (SLOTLOCK_INVITATION_EMAIL, _KEY, _FROM). */
3
+ export interface SlotlockInvitationEmailConfig {
4
+ provider: 'resend' | 'postmark';
5
+ /** Resend's API key, or Postmark's server token. */
6
+ key: string;
7
+ /** The sender, as the provider has it verified: `Slotlock <team@example.com>` or an address. */
8
+ from: string;
9
+ }
10
+ type Invitation = Parameters<SlotlockInvitationSender['send']>[0];
11
+ /** A provider's refusal: its name and HTTP status alone (its text can echo the address). */
12
+ export declare class SlotlockInvitationEmailError extends Error {
13
+ name: string;
14
+ }
15
+ /** The message an invitation sends: subject, text and HTML saying the same, with no marketing. */
16
+ export declare function slotlockInvitationEmail(invitation: Invitation, brandName?: string): {
17
+ subject: string;
18
+ text: string;
19
+ html: string;
20
+ };
21
+ /** Sends each invitation once through the provider; throws on any answer but success. */
22
+ export declare function createSlotlockInvitationEmailSender(config: SlotlockInvitationEmailConfig, options?: {
23
+ fetch?: typeof fetch;
24
+ brandName?: string;
25
+ }): SlotlockInvitationSender;
26
+ export {};
@@ -0,0 +1,73 @@
1
+ // Team invitations by email (dashboard.ts SlotlockInvitationSender): a plain transactional message
2
+ // with the link, sent through Resend or Postmark with the deployment's own key. Nothing else is
3
+ // sent, and no address is kept by this module.
4
+ import { createHash } from 'node:crypto';
5
+ /** A provider's refusal: its name and HTTP status alone (its text can echo the address). */
6
+ export class SlotlockInvitationEmailError extends Error {
7
+ name = 'SlotlockInvitationEmailError';
8
+ }
9
+ /** Resend refuses a request without a User-Agent (403), and a Worker's fetch sends none. */
10
+ const USER_AGENT = 'Slotlock (+https://slotlock.pylota.io)';
11
+ const escape = (value) => value.replace(/[&<>"']/g, (character) => `&#${character.charCodeAt(0)};`);
12
+ /** The message an invitation sends: subject, text and HTML saying the same, with no marketing. */
13
+ export function slotlockInvitationEmail(invitation, brandName = 'Slotlock') {
14
+ const until = `${invitation.expiresAt.toISOString().slice(0, 16).replace('T', ' ')} UTC`;
15
+ const subject = `${invitation.inviter} invited you to ${invitation.workspace} on ${brandName}`;
16
+ const lines = [
17
+ `${invitation.inviter} invited you to join ${invitation.workspace} on ${brandName} as ${invitation.role}.`,
18
+ `Join: ${invitation.link}`,
19
+ `The link works once, for this address, until ${until}. If you did not expect it, ignore this email: nothing happens unless you use the link.`,
20
+ ];
21
+ const html = `<!doctype html><html><body style="font-family:system-ui,sans-serif;line-height:1.5;color:#111"><p>${escape(lines[0])}</p><p><a href="${escape(invitation.link)}">Join ${escape(invitation.workspace)}</a></p><p style="color:#555;font-size:14px">${escape(lines[2])}</p></body></html>`;
22
+ return { subject, text: `${lines.join('\n\n')}\n`, html };
23
+ }
24
+ /** Sends each invitation once through the provider; throws on any answer but success. */
25
+ export function createSlotlockInvitationEmailSender(config, options = {}) {
26
+ const fetchImpl = options.fetch ?? fetch;
27
+ return {
28
+ async send(invitation) {
29
+ const message = slotlockInvitationEmail(invitation, options.brandName);
30
+ const request = config.provider === 'resend'
31
+ ? {
32
+ url: 'https://api.resend.com/emails',
33
+ headers: {
34
+ authorization: `Bearer ${config.key}`,
35
+ 'content-type': 'application/json',
36
+ 'user-agent': USER_AGENT,
37
+ // One send per link, however often the person inviting submits: keyed by the link,
38
+ // as a digest, so the key names no address.
39
+ 'idempotency-key': createHash('sha256').update(invitation.link, 'utf8').digest('hex'),
40
+ },
41
+ body: { from: config.from, to: [invitation.to], subject: message.subject, text: message.text, html: message.html },
42
+ }
43
+ : {
44
+ url: 'https://api.postmarkapp.com/email',
45
+ headers: {
46
+ 'x-postmark-server-token': config.key,
47
+ 'content-type': 'application/json',
48
+ accept: 'application/json',
49
+ 'user-agent': USER_AGENT,
50
+ },
51
+ body: {
52
+ From: config.from,
53
+ To: invitation.to,
54
+ Subject: message.subject,
55
+ TextBody: message.text,
56
+ HtmlBody: message.html,
57
+ MessageStream: 'outbound',
58
+ },
59
+ };
60
+ const response = await fetchImpl(request.url, {
61
+ method: 'POST',
62
+ headers: request.headers,
63
+ body: JSON.stringify(request.body),
64
+ redirect: 'manual',
65
+ signal: AbortSignal.timeout(10_000),
66
+ });
67
+ if (!response.ok) {
68
+ // The status alone: a provider's error text can echo the address.
69
+ throw new SlotlockInvitationEmailError(`${config.provider === 'resend' ? 'Resend' : 'Postmark'} answered HTTP ${response.status}`);
70
+ }
71
+ },
72
+ };
73
+ }
package/maintenance.d.ts CHANGED
@@ -9,8 +9,8 @@ export interface SlotlockMaintenanceResult {
9
9
  refused: number;
10
10
  /**
11
11
  * Rows deleted: expired holds and their keyed answers; past retention, event idempotency answers,
12
- * agent event cancellations, stray booking retirements and feed changes; and expired assistant
13
- * conversations. A cancelled booking's tombstone stays.
12
+ * agent event cancellations, stray booking retirements and feed changes; expired assistant
13
+ * conversations; and audit events past retention. A cancelled booking's tombstone stays.
14
14
  */
15
15
  pruned: number;
16
16
  /**
@@ -102,6 +102,11 @@ export interface SlotlockMaintenanceSchedulerOptions {
102
102
  usage?: boolean;
103
103
  /** Fetches holiday calendar subscriptions due to be imported again (maintainSlotlockTenant). */
104
104
  holidayFetch?: typeof fetch;
105
+ /**
106
+ * Team workspaces' verified domains looked up again (workspace-domains.ts runSlotlockDomainChecks):
107
+ * run with each sweep, at most every quarter hour. What it throws goes to onError.
108
+ */
109
+ domainChecks?: () => Promise<unknown>;
105
110
  /** A pass that ran: counts only, never the tenant. Not awaited; what it throws is ignored. */
106
111
  onPass?: (result: SlotlockMaintenanceResult) => void;
107
112
  /**
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, buildSlotlockTenantExport, calendarEventRollingHorizon, } from './store.js';
3
3
  import { readSlotlockHolidayCalendar } from './holiday-import.js';
4
4
  /**
5
5
  * One maintenance pass for a tenant: roll its recurring events to the horizon, then purge expired
@@ -73,6 +73,15 @@ export async function maintainSlotlockTenant(store, tenantRef, options = {}) {
73
73
  options.signal?.throwIfAborted();
74
74
  // Conversations expire 2 hours after their last message (dashboard.ts).
75
75
  pruned += await withTenant((tenant) => tenant.pruneAssistantConversations({ tenantRef }));
76
+ // Audit events past retention (SLOTLOCK_AUDIT_RETENTION_DAYS, 180).
77
+ pruned += await withTenant((tenant) => tenant.pruneAuditEvents({ tenantRef }));
78
+ // Exports past their time go; one a stopped server left pending is built now (RFC 0004 D3).
79
+ const exports = await withTenant((tenant) => tenant.pruneTenantExports({ tenantRef }));
80
+ pruned += exports.deleted;
81
+ for (const exportId of exports.pending) {
82
+ options.signal?.throwIfAborted();
83
+ await buildSlotlockTenantExport(store, tenantRef, exportId);
84
+ }
76
85
  const zones = await withTenant((tenant) => tenant.followZoneRules({ tenantRef, move: options.followZoneRules === true }));
77
86
  const staleSources = await withTenant((tenant) => tenant.reportStaleCoverage({ tenantRef }));
78
87
  // Approvals past their wait, and no-shows, first: the time they free is offered to the waitlist
@@ -293,6 +302,11 @@ export function startSlotlockMaintenance(options) {
293
302
  return;
294
303
  lastSweep = Date.now();
295
304
  options.store.sweepAssistantConversations().catch(report);
305
+ // Team invitations finished a month ago, with the addresses they were sent to, and sessions
306
+ // ended a day ago.
307
+ options.store.sweepDashboardRecords().catch(report);
308
+ // Verified domains whose daily check (or a pending claim's hourly one) is due.
309
+ options.domainChecks?.().catch(report);
296
310
  };
297
311
  // Every tenant with data, once an interval: those no key has called since the server started too.
298
312
  let lastDiscovery = Number.NEGATIVE_INFINITY;
package/oidc.d.ts ADDED
@@ -0,0 +1,135 @@
1
+ /** The algorithms Slotlock verifies: RSA PKCS#1 v1.5, RSA-PSS and ECDSA P-256, all with SHA-256. */
2
+ export declare const SLOTLOCK_OIDC_ALGORITHMS: readonly ["RS256", "PS256", "ES256"];
3
+ export type SlotlockOidcAlgorithm = (typeof SLOTLOCK_OIDC_ALGORITHMS)[number];
4
+ /** Why an ID token was refused (never shown with the token). */
5
+ export type SlotlockIdTokenRefusal = 'malformed' | 'algorithm' | 'key' | 'signature' | 'issuer' | 'audience' | 'expired' | 'not_yet_valid' | 'issued_at' | 'nonce' | 'subject';
6
+ /** RFC 4648 base64url, without padding, to bytes; null when it is not that. */
7
+ export declare function slotlockBase64UrlBytes(text: string): Uint8Array<ArrayBuffer> | null;
8
+ /** Bytes as RFC 4648 base64url, without padding. */
9
+ export declare function slotlockBase64Url(bytes: Uint8Array): string;
10
+ /** A JWK as an issuer publishes it. */
11
+ export interface SlotlockJwk {
12
+ kty?: unknown;
13
+ kid?: unknown;
14
+ use?: unknown;
15
+ alg?: unknown;
16
+ n?: unknown;
17
+ e?: unknown;
18
+ crv?: unknown;
19
+ x?: unknown;
20
+ y?: unknown;
21
+ }
22
+ /**
23
+ * A published key as a verification key for `alg`, or null when it does not fit it: RSA (at least
24
+ * 2048 bits) for RS256 and PS256, EC P-256 for ES256; `use`, when given, `sig`; `alg`, when given,
25
+ * the token's.
26
+ */
27
+ export declare function importSlotlockJwk(jwk: SlotlockJwk, alg: SlotlockOidcAlgorithm): Promise<CryptoKey | null>;
28
+ /** Finds the key a token names: by issuer's JWKS and key id (see createSlotlockJwksCache). */
29
+ export type SlotlockKeyLookup = (kid: string | null, alg: SlotlockOidcAlgorithm) => Promise<CryptoKey | null>;
30
+ /** What an ID token is checked against. */
31
+ export interface SlotlockIdTokenExpectation {
32
+ /** The exact issuer, or the exact spellings one issuer documents (Google has two). */
33
+ issuers: readonly string[];
34
+ clientId: string;
35
+ /** The algorithms this issuer may sign with: a subset of SLOTLOCK_OIDC_ALGORITHMS. */
36
+ algorithms: readonly SlotlockOidcAlgorithm[];
37
+ keys: SlotlockKeyLookup;
38
+ nonce: string;
39
+ /** Milliseconds since the epoch. */
40
+ now: number;
41
+ }
42
+ /** An ID token checked: its claims, all of them, for the caller to read what it needs. */
43
+ export type SlotlockIdTokenResult = {
44
+ claims: Record<string, unknown>;
45
+ } | {
46
+ refusal: SlotlockIdTokenRefusal;
47
+ };
48
+ /** Check an ID token: its signature first, then its claims, in OpenID Connect Core's order. */
49
+ export declare function verifySlotlockIdToken(token: string, expected: SlotlockIdTokenExpectation): Promise<SlotlockIdTokenResult>;
50
+ /** Why a URL an Owner gave, or a document fetched from one, was refused. */
51
+ export declare class SlotlockOidcFetchError extends Error {
52
+ readonly code: 'url' | 'redirect' | 'status' | 'type' | 'size' | 'json' | 'network';
53
+ constructor(code: 'url' | 'redirect' | 'status' | 'type' | 'size' | 'json' | 'network', message: string);
54
+ }
55
+ /**
56
+ * Whether a URL is one Slotlock fetches for a team: https on the default port, a host name (never
57
+ * an IP literal), never localhost, a single label, or a private-use suffix; no credentials.
58
+ */
59
+ export declare function slotlockOidcUrlAllowed(value: string): URL | null;
60
+ /**
61
+ * GET a JSON document from a URL an Owner influenced: refused unless slotlockOidcUrlAllowed; no
62
+ * redirect followed (any 3xx refuses); 5 seconds; 256 KB; a JSON content type. On Node, pass a
63
+ * fetch that refuses private addresses (webhooks.ts createSlotlockGuardedFetch).
64
+ */
65
+ export declare function slotlockFetchOidcJson(value: string, options?: {
66
+ fetch?: typeof fetch;
67
+ timeoutMs?: number;
68
+ maxBytes?: number;
69
+ }): Promise<{
70
+ body: Record<string, unknown>;
71
+ maxAge: number | null;
72
+ }>;
73
+ /**
74
+ * A JSON object answered by a host an Owner chose, read to at most maxBytes (the declared length
75
+ * first, then as it streams), so a provider cannot make the server buffer an endless answer.
76
+ */
77
+ export declare function slotlockReadJsonBody(response: Response, maxBytes: number): Promise<Record<string, unknown>>;
78
+ /** An issuer's endpoints, as its discovery document gave them when an Owner saved the connection. */
79
+ export interface SlotlockOidcProvider {
80
+ issuer: string;
81
+ authorizationEndpoint: string;
82
+ tokenEndpoint: string;
83
+ jwksUri: string;
84
+ algorithms: SlotlockOidcAlgorithm[];
85
+ /** RFC 9207: the issuer returns `iss` with the code, and Slotlock then checks it. */
86
+ issParameter: boolean;
87
+ }
88
+ /**
89
+ * An issuer's discovery document (OpenID Connect Discovery 1.0): its `issuer` must be exactly the
90
+ * one entered, its endpoints https on public host names, and it must sign with an algorithm Slotlock
91
+ * verifies.
92
+ */
93
+ export declare function discoverSlotlockOidc(issuer: string, options?: {
94
+ fetch?: typeof fetch;
95
+ }): Promise<SlotlockOidcProvider>;
96
+ /**
97
+ * An Entra ID tenant's v2.0 issuer, pinned to its GUID: never `common`, `organizations`,
98
+ * `consumers`, a domain name or the consumer tenant, so the multi-tenant `{tenantid}` issuer never
99
+ * exists in Slotlock. Null when the tenant is not one.
100
+ */
101
+ export declare function slotlockEntraIssuer(tenant: string): string | null;
102
+ /** Google's issuer, in the two spellings it documents. */
103
+ export declare const SLOTLOCK_GOOGLE_ISSUERS: readonly string[];
104
+ /**
105
+ * Issuers' keys, by JWKS URL: kept for the answer's max-age (5 minutes to 24 hours). A key id not
106
+ * among them is fetched again at most once a minute per URL, one fetch at a time; while the URL
107
+ * fails, a set fetched within 24 hours still serves the keys it holds. At most `maxUrls` URLs and 20
108
+ * keys each are kept, only the fields verifying needs: the URLs are Owners' to choose.
109
+ */
110
+ export declare function createSlotlockJwksCache(options: {
111
+ fetchJson?: (url: string) => Promise<{
112
+ body: Record<string, unknown>;
113
+ maxAge: number | null;
114
+ }>;
115
+ fetch?: typeof fetch;
116
+ now?: () => number;
117
+ /** How many JWKS URLs are kept (100): the least recently used goes first. */
118
+ maxUrls?: number;
119
+ }): {
120
+ /** The key `kid` names at `url` for `alg`, or null. */
121
+ key(url: string, kid: string | null, alg: SlotlockOidcAlgorithm): Promise<CryptoKey | null>;
122
+ /** A lookup bound to one JWKS URL, for verifySlotlockIdToken. */
123
+ lookup(url: string): SlotlockKeyLookup;
124
+ };
125
+ export type SlotlockJwksCache = ReturnType<typeof createSlotlockJwksCache>;
126
+ /**
127
+ * Seals and opens a team's client secret: AES-256-GCM under a key derived (HKDF-SHA-256) from
128
+ * SLOTLOCK_SSO_SECRET, a random 12-byte IV each time, and the team and connection as associated
129
+ * data, so a sealed secret moved to another row does not open.
130
+ */
131
+ export declare function createSlotlockSecretBox(secret: string): {
132
+ seal(plaintext: string, associated: string): Promise<string>;
133
+ open(sealed: string, associated: string): Promise<string | null>;
134
+ };
135
+ export type SlotlockSecretBox = ReturnType<typeof createSlotlockSecretBox>;