@protobase/server 0.59.1 → 0.59.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.
Files changed (45) hide show
  1. package/dist/better-auth/store/migrations-table.js +1 -1
  2. package/dist/channels/binary-frame.js +44 -0
  3. package/dist/channels/channel-protocol.js +16 -3
  4. package/dist/channels/channel-refusals.js +1 -1
  5. package/dist/channels/channel-routes.js +22 -9
  6. package/dist/channels/channel-sender.d.ts +1 -1
  7. package/dist/channels/channel-sender.js +24 -12
  8. package/dist/channels/channel-socket.js +12 -7
  9. package/dist/channels/database-sends.js +57 -0
  10. package/dist/channels/sends-prune.js +15 -0
  11. package/dist/channels/sends-schema-sql.js +30 -0
  12. package/dist/channels/sends-schema.d.ts +7 -0
  13. package/dist/channels/sends-schema.js +18 -0
  14. package/dist/channels/sent.js +17 -1
  15. package/dist/create-admin.d.ts +5 -2
  16. package/dist/create-admin.js +33 -4
  17. package/dist/deps.d.ts +2 -0
  18. package/dist/index.d.ts +6 -2
  19. package/dist/index.js +6 -2
  20. package/dist/internal.d.ts +2 -1
  21. package/dist/internal.js +2 -1
  22. package/dist/meta.d.ts +2 -0
  23. package/dist/meta.js +1 -0
  24. package/dist/notifications/caller.js +2 -2
  25. package/dist/notifications/deliver.js +14 -2
  26. package/dist/notifications/push-job.js +5 -3
  27. package/dist/notifications/schema.js +7 -4
  28. package/dist/package.js +1 -1
  29. package/dist/realtime/database-broadcasts.js +2 -25
  30. package/dist/realtime/followed-poll.js +34 -0
  31. package/dist/realtime/realtime-prune.d.ts +5 -3
  32. package/dist/realtime/realtime-prune.js +8 -3
  33. package/dist/realtime/relay/realtime-options.d.ts +7 -0
  34. package/dist/realtime/relay/realtime-options.js +7 -1
  35. package/dist/status/do-not-disturb.d.ts +9 -0
  36. package/dist/status/do-not-disturb.js +15 -0
  37. package/dist/status/options.d.ts +15 -0
  38. package/dist/status/options.js +25 -0
  39. package/dist/status/routes.js +38 -0
  40. package/dist/status/schema-sql.js +19 -0
  41. package/dist/status/schema.d.ts +12 -0
  42. package/dist/status/schema.js +33 -0
  43. package/dist/status/status-input.js +33 -0
  44. package/dist/status/status-store.js +39 -0
  45. package/package.json +4 -4
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { MobileConfig } from "./mobile-config.js";
2
2
  import { AppIdentity } from "./app-identity.js";
3
3
  import { AppIcons, PngIcon, pngIcons } from "./web-app/app-icons.js";
4
- import { RealtimeOptions, relayed } from "./realtime/relay/realtime-options.js";
4
+ import { RealtimeOptions, relayed, sqlSends } from "./realtime/relay/realtime-options.js";
5
5
  import { EventsOptions } from "./changes/events-options.js";
6
6
  import { DecryptField, FieldDecrypter, FieldReader, RecordKey } from "./encryption/decrypt-field-types.js";
7
7
  import { StandardSchema, readJson } from "./functions/read-json.js";
@@ -22,6 +22,7 @@ import { HistoryStore, maxHistoryPage, maxUnreadCount } from "./notifications/hi
22
22
  import { PushAnswer, PushSender } from "./notifications/sender.js";
23
23
  import { DeviceColumn, HistoryColumn, NotificationsOptions } from "./notifications/options.js";
24
24
  import { CallBackend, CallsOptions, LiveKitBackend } from "./calls/calls-options.js";
25
+ import { StatusOptions, statusNames } from "./status/options.js";
25
26
  import { EventData, RunContext, SubscriptionDefinition, SubscriptionEvent, SubscriptionOptions, subscription } from "./webhooks/subscription.js";
26
27
  import { Sharp, renderVariant, thumbHash } from "./files/images.js";
27
28
  import { FileProvider, FileStore, StoredObject } from "./files/store.js";
@@ -101,6 +102,7 @@ import { parseColumns, readOutbox } from "./changes/outbox.js";
101
102
  import { newWebhookSecret } from "./webhooks/standard-webhooks.js";
102
103
  import { checkWebhooksSchema, webhooksMigration } from "./webhooks/table/schema.js";
103
104
  import { checkRealtimeRelay, realtimeRelayMigration } from "./realtime/relay/relay-schema.js";
105
+ import { channelSendsMigration, checkChannelSends } from "./channels/sends-schema.js";
104
106
  import { checkComments, commentsMigration } from "./comments/comments-sql.js";
105
107
  import { CheckAiInput, checkAi } from "./ai/check-ai.js";
106
108
  import { CommentItem } from "./comments/comment-item.js";
@@ -117,6 +119,8 @@ import { sendNotification } from "./notifications/send-notification.js";
117
119
  import { VapidSettings, readVapidSettings, vapidPrivateKeyVariable, vapidPublicKeyVariable, vapidSubjectVariable } from "./notifications/web-push/vapid-settings.js";
118
120
  import { WebPushOptions, webPushSender } from "./notifications/web-push/web-push-sender.js";
119
121
  import { generateVapidKeys } from "./notifications/web-push/keys.js";
122
+ import { checkStatusSchema, statusMigration } from "./status/schema.js";
123
+ import { onDoNotDisturb } from "./status/do-not-disturb.js";
120
124
  import { MemberInput, NewOrganization, addAppRoleToOwners, addMember, createOrganization, listOrganizations, removeMember, setMemberRoles } from "./better-auth/organizations/host.js";
121
125
  import { AssistantBackend, assistantProtocolRoutes } from "./assistant/protocol-routes.js";
122
126
  import { fileConversationStore } from "./assistant/file-conversation-store.js";
@@ -175,4 +179,4 @@ import { DrainResult, JobsRuntime, StartJobsOptions, startJobs } from "./jobs/ru
175
179
  import { jobsDrainApp } from "./jobs/drain-route.js";
176
180
  import { checkJobsSchema, jobsSchema, jobsSchemaMigration } from "./jobs/schema.js";
177
181
  import { AdminAuth, CreateAuthOptions, UserCreatedHook, normalizeRoles } from "./better-auth/create-auth.js";
178
- export { type Admin, type AdminAuth, type AdminOptions, type AggregateParams, type ApiFunction, type ApiKeys, type ApiKeysOptions, type ApnsEnvironment, type AppIcons, type AppIdentity, type AppPoolConfig, type ApplyOptions, type ApplyResult, type ApprovalAnswer, type ApprovalRequest, type ArgumentsPayload, type AssistantBackend, type AssistantOptions, type AssistantSettings, type AssistantTool, type AuditEvent, type AuditQueue, type AuthHooks, type AuthSettings, type AuthenticateSettings, type Authenticator, type BaseToolContext, type BatchOperation, type BatchResult, type BetterAuthAuthenticator, type BetterAuthAuthenticatorOptions, type CallBackend, type CallMember, type Caller, type CallerOptions, type CallsAccess, type CallsOptions, type CapturedRow, type CascadingKey, type ChannelBroadcast, type ChannelSender, type ChatMessage, type ChatTextPart, type CheckAiInput, type CleanupEntry, type CleanupReport, type CleanupSchedule, type CoalescedChange, type CommentItem, type CommentToolsInput, type CommentUsers, type Completion, type CompletionOptions, ConnectionsBusy, type Conversation, type ConversationRecord, type ConversationStore, type ConversationsOptions, type CorsOptions, type CorsOrigins, type CreateAdminInput, type CreateAuthOptions, type DecryptField, type Device, type DeviceColumn, type DeviceKind, type DeviceStore, DeviceStoreError, type DrainResult, type EmailComponent, type EmailOptions, type EmailTemplate, type EncryptionStatus, type EncryptionTools, type EncryptionToolsInput, type EnqueueJob, type EnqueueOptions, type ErrorReporter, type ErrorReportingOptions, type ErrorReportingSettings, type EventData, type EventsOptions, FatalJobError, type FieldApplied, type FieldDecrypter, type FieldReader, type FieldStatus, type FileCleanupInput, type FileLimits, type FileObject, type FileProvider, type FileProviderSettings, type FileRef, type FileStore, type FileVariantObject, type FilesOptions, type FunctionApp, type FunctionCaller, type FunctionContext, type FunctionEnv, type FunctionHandler, type FunctionOptions, type FunctionRecords, type FunctionSocket, type FunctionSource, type HistoryColumn, type HistoryOptions, type HistoryPosition, type HistoryStore, HistoryStoreError, type HookContext, HttpProblem, type IssueTokenOptions, type JobContext, type JobDeclaration, type JobDefinition, type JobName, type JobOptions, type JobPayload, type JobRun, type JobsRuntime, type JsonObject, type JsonValue, type JwtOptions, type JwtVerifyOptions, type KeyFunctionContext, type LinkedAccount, type ListParams, type LiveKitBackend, type LocalFilesOptions, type MailMessage, type Mailer, type MapLayer, type MapOptions, type MapSettings, type McpApprovals, type McpGateInput, type McpHandlerInput, type McpRoutesInput, type McpServerInput, type McpToolsInput, type McpWrites, type MemberInput, type Membership, MissingCredentials, type MobileConfig, ModelError, type ModelSettings, type NewDevice, type NewNotification, type NewOrganization, type NewUser, type NotificationRecipients, NotificationSendError, type NotificationsOptions, type OperatorProvider, type OrganizationRole, type OrganizationsOptions, type PgModule, type PipelineHook, type PlatformSignIn, type PluginOption, type PluginRef, type PngIcon, type ProtectedFunctionOptions, type PublicFunctionContext, type PublicFunctionEnv, type PublicFunctionHandler, type PublicFunctionOptions, type PushAnswer, type PushMessage, PushMessageError, type PushSender, type QueryOptions, type QueryResult, type ReadableResources, type RealtimeOptions, type RealtimePrune, type ReasoningDetail, type ReasoningEffort, type RebindOption, type RecordGroups, type RecordKey, type RecordKeyInput, type RecordPage, type RenderedEmail, type ResourceChange, type ResourceSource, type RoleDefinition, type RolesInput, type RunContext, type RuntimeOptions, type S3Bucket, type S3FilesOptions, type S3ListPage, type S3Object, type ScanGuardMode, type ScheduledDelete, type SendNotification, type Session, type SessionOrganization, type Sharp, type SignInPolicy, type SignInRule, type SignatureOptions, type SocketEvents, type SourceOption, type SourceRef, type StaffAccessRule, type StaffSignIn, type StandardSchema, type StandardWebhookOptions, type StartJobsOptions, type StoreFileInput, type StoredMember, type StoredNotification, type StoredObject, type StoredOrganization, type StoredUser, type StrayValues, type SubscriptionDefinition, type SubscriptionEvent, type SubscriptionOptions, type TenantValue, type ToolAnnotations, type ToolCall, type ToolComment, type ToolCommentPage, type ToolComments, type ToolContext, type ToolRecord, type ToolRecords, type ToolSpec, type TrustedApp, type TurnOptions, type UnreadableEntry, type Urgency, type User, type UserCreatedHook, type VapidSettings, type ViewSource, type WebPushFields, type WebPushOptions, type WebhooksOptions, type WidgetOptions, type WriteEvent, type WriteOperation, type WriteRecordToolsInput, addAppRoleToOwners, addCommentTool, addMember, apiKeyCaller, apiKeySettings, apiKeyVariables, appNameOver, aspectRatio, assistantProtocolRoutes, assistantRoles, assistantSettings, assistantVariables, authSchemaDrift, authSchemaMigration, authStoreTables, backgroundSettled, badRequest, batchWriteTool, beforeSignInPluginsVariable, betterAuthTokens, capSocketMessages, changesMigration, checkAi, checkAuthSchema, checkChanges, checkComments, checkComputed, checkJobsSchema, checkNotificationsSchema, checkQueryRunner, checkRealtimeRelay, checkResourcesOutsideAuthStore, checkSourceNames, checkViewQueries, checkWebhooksSchema, coalesce, commentTools, commentsMigration, computedMigration, configExports, consoleAuditQueue, conversationKey, countRecordsTool, createAdmin, createConversations, createEncryptionTools, createFileCleanup, createOrganization, createRecordTool, createUser, defaultAppName, defaultHistoryPage, defaultSignInPolicy, defineEmail, defineFunction, defineJob, defineTool, deleteRecordTool, deleteUser, describeResourceTool, deviceIdParam, deviceStore, devicesPath, enqueueJob, errorReportingSettings, errorReportingVariable, eventMatcher, fileConversationStore, fileSize, fileType, forbidden, formatUri, functionApp, generateVapidKeys, getRecordTool, goTruePath, handleDroppedConnections, hasUsers, historyIdParam, historyIdPattern, historyLimits, historyPath, historyStore, honorSocketMessageCaps, imageSize, isBackgroundWork, isCaptured, isJobDefinition, isOAuthWellKnownPath, isSchedule, issueToken, jobDefinitions, jobsDrainApp, jobsSchema, jobsSchemaMigration, jwtAuthenticator, jwtVerifier, limitedFileStore, listCommentsTool, listOrganizations, listRecordsTool, listResourcesTool, listUsers, localFileStore, longQueryTimeoutMs, mapSettings, mapStyleUrl, mapStyleVariable, maxDevicesPerUser, maxHistoryPage, maxMessageBytes, maxRecipients, maxTokenTtlSeconds, maxTtlSeconds, maxUnreadCount, maxWidgetPropsBytes, mcpApprovals, mcpGate, mcpHandler, mcpIcons, mcpInstructions, mcpRoutes, mcpServer, mcpToolContext, mcpTools, migrationCommand, newWebhookSecret, normalizeRoles, notFound, notificationSenders, notificationsMigration, oauthWellKnownPaths, objectKey, parseColumns, parseRoles, passwordLength, pluginOptions, pluginsBeforeSignIn, pluginsFor, pluginsVariable, pngIcons, poolWaitMs, postgresDeviceStore, postgresHistoryStore, processor, pushMessage, queryRunnerMigration, readDbConnections, readJson, readOnlyQueryTool, readOperatorSettings, readOutbox, readPlatformSignIn, readRecordTools, readVapidSettings, readWriteQueryTool, readableResources, realtimeLimits, realtimeRelayMigration, recordOf, refusalOf, refused, relayed, removeDevicesWithUsers, removeHistoryWithUsers, removeMember, renderVariant, requestApproval, requireEnv, resourceChange, roleChoices, runInBackground, runReadOnlyQuery, runReadWriteQuery, runTurn, runtimeUrl, runtimeVariable, s3FileStore, seesAssistant, seesRuntime, sendNotification, sessionOf, setMemberRoles, setUserBanned, setUserRole, shownAppName, signInPolicyMethods, socketMessageCapOf, sourceOptions, sourcesFor, sourcesVariable, startErrorReporting, startJobs, storageCleanupSchedule, storageProvider, storeFile, storeUsage, streamChatCompletion, subscription, supabasePath, thumbHash, unauthorized, updateRecordTool, urgencies, vapidPrivateKeyVariable, vapidPublicKeyVariable, vapidSubjectVariable, variantPath, verifySignature, verifyStandardWebhook, waitUntil, wantsLongQueries, webManifestPath, webPushSender, webauthnPath, webhooksMigration, widget, withCacheBreakpoints, withTrustedApps, writeRecordTools };
182
+ export { type Admin, type AdminAuth, type AdminOptions, type AggregateParams, type ApiFunction, type ApiKeys, type ApiKeysOptions, type ApnsEnvironment, type AppIcons, type AppIdentity, type AppPoolConfig, type ApplyOptions, type ApplyResult, type ApprovalAnswer, type ApprovalRequest, type ArgumentsPayload, type AssistantBackend, type AssistantOptions, type AssistantSettings, type AssistantTool, type AuditEvent, type AuditQueue, type AuthHooks, type AuthSettings, type AuthenticateSettings, type Authenticator, type BaseToolContext, type BatchOperation, type BatchResult, type BetterAuthAuthenticator, type BetterAuthAuthenticatorOptions, type CallBackend, type CallMember, type Caller, type CallerOptions, type CallsAccess, type CallsOptions, type CapturedRow, type CascadingKey, type ChannelBroadcast, type ChannelSender, type ChatMessage, type ChatTextPart, type CheckAiInput, type CleanupEntry, type CleanupReport, type CleanupSchedule, type CoalescedChange, type CommentItem, type CommentToolsInput, type CommentUsers, type Completion, type CompletionOptions, ConnectionsBusy, type Conversation, type ConversationRecord, type ConversationStore, type ConversationsOptions, type CorsOptions, type CorsOrigins, type CreateAdminInput, type CreateAuthOptions, type DecryptField, type Device, type DeviceColumn, type DeviceKind, type DeviceStore, DeviceStoreError, type DrainResult, type EmailComponent, type EmailOptions, type EmailTemplate, type EncryptionStatus, type EncryptionTools, type EncryptionToolsInput, type EnqueueJob, type EnqueueOptions, type ErrorReporter, type ErrorReportingOptions, type ErrorReportingSettings, type EventData, type EventsOptions, FatalJobError, type FieldApplied, type FieldDecrypter, type FieldReader, type FieldStatus, type FileCleanupInput, type FileLimits, type FileObject, type FileProvider, type FileProviderSettings, type FileRef, type FileStore, type FileVariantObject, type FilesOptions, type FunctionApp, type FunctionCaller, type FunctionContext, type FunctionEnv, type FunctionHandler, type FunctionOptions, type FunctionRecords, type FunctionSocket, type FunctionSource, type HistoryColumn, type HistoryOptions, type HistoryPosition, type HistoryStore, HistoryStoreError, type HookContext, HttpProblem, type IssueTokenOptions, type JobContext, type JobDeclaration, type JobDefinition, type JobName, type JobOptions, type JobPayload, type JobRun, type JobsRuntime, type JsonObject, type JsonValue, type JwtOptions, type JwtVerifyOptions, type KeyFunctionContext, type LinkedAccount, type ListParams, type LiveKitBackend, type LocalFilesOptions, type MailMessage, type Mailer, type MapLayer, type MapOptions, type MapSettings, type McpApprovals, type McpGateInput, type McpHandlerInput, type McpRoutesInput, type McpServerInput, type McpToolsInput, type McpWrites, type MemberInput, type Membership, MissingCredentials, type MobileConfig, ModelError, type ModelSettings, type NewDevice, type NewNotification, type NewOrganization, type NewUser, type NotificationRecipients, NotificationSendError, type NotificationsOptions, type OperatorProvider, type OrganizationRole, type OrganizationsOptions, type PgModule, type PipelineHook, type PlatformSignIn, type PluginOption, type PluginRef, type PngIcon, type ProtectedFunctionOptions, type PublicFunctionContext, type PublicFunctionEnv, type PublicFunctionHandler, type PublicFunctionOptions, type PushAnswer, type PushMessage, PushMessageError, type PushSender, type QueryOptions, type QueryResult, type ReadableResources, type RealtimeOptions, type RealtimePrune, type ReasoningDetail, type ReasoningEffort, type RebindOption, type RecordGroups, type RecordKey, type RecordKeyInput, type RecordPage, type RenderedEmail, type ResourceChange, type ResourceSource, type RoleDefinition, type RolesInput, type RunContext, type RuntimeOptions, type S3Bucket, type S3FilesOptions, type S3ListPage, type S3Object, type ScanGuardMode, type ScheduledDelete, type SendNotification, type Session, type SessionOrganization, type Sharp, type SignInPolicy, type SignInRule, type SignatureOptions, type SocketEvents, type SourceOption, type SourceRef, type StaffAccessRule, type StaffSignIn, type StandardSchema, type StandardWebhookOptions, type StartJobsOptions, type StatusOptions, type StoreFileInput, type StoredMember, type StoredNotification, type StoredObject, type StoredOrganization, type StoredUser, type StrayValues, type SubscriptionDefinition, type SubscriptionEvent, type SubscriptionOptions, type TenantValue, type ToolAnnotations, type ToolCall, type ToolComment, type ToolCommentPage, type ToolComments, type ToolContext, type ToolRecord, type ToolRecords, type ToolSpec, type TrustedApp, type TurnOptions, type UnreadableEntry, type Urgency, type User, type UserCreatedHook, type VapidSettings, type ViewSource, type WebPushFields, type WebPushOptions, type WebhooksOptions, type WidgetOptions, type WriteEvent, type WriteOperation, type WriteRecordToolsInput, addAppRoleToOwners, addCommentTool, addMember, apiKeyCaller, apiKeySettings, apiKeyVariables, appNameOver, aspectRatio, assistantProtocolRoutes, assistantRoles, assistantSettings, assistantVariables, authSchemaDrift, authSchemaMigration, authStoreTables, backgroundSettled, badRequest, batchWriteTool, beforeSignInPluginsVariable, betterAuthTokens, capSocketMessages, changesMigration, channelSendsMigration, checkAi, checkAuthSchema, checkChanges, checkChannelSends, checkComments, checkComputed, checkJobsSchema, checkNotificationsSchema, checkQueryRunner, checkRealtimeRelay, checkResourcesOutsideAuthStore, checkSourceNames, checkStatusSchema, checkViewQueries, checkWebhooksSchema, coalesce, commentTools, commentsMigration, computedMigration, configExports, consoleAuditQueue, conversationKey, countRecordsTool, createAdmin, createConversations, createEncryptionTools, createFileCleanup, createOrganization, createRecordTool, createUser, defaultAppName, defaultHistoryPage, defaultSignInPolicy, defineEmail, defineFunction, defineJob, defineTool, deleteRecordTool, deleteUser, describeResourceTool, deviceIdParam, deviceStore, devicesPath, enqueueJob, errorReportingSettings, errorReportingVariable, eventMatcher, fileConversationStore, fileSize, fileType, forbidden, formatUri, functionApp, generateVapidKeys, getRecordTool, goTruePath, handleDroppedConnections, hasUsers, historyIdParam, historyIdPattern, historyLimits, historyPath, historyStore, honorSocketMessageCaps, imageSize, isBackgroundWork, isCaptured, isJobDefinition, isOAuthWellKnownPath, isSchedule, issueToken, jobDefinitions, jobsDrainApp, jobsSchema, jobsSchemaMigration, jwtAuthenticator, jwtVerifier, limitedFileStore, listCommentsTool, listOrganizations, listRecordsTool, listResourcesTool, listUsers, localFileStore, longQueryTimeoutMs, mapSettings, mapStyleUrl, mapStyleVariable, maxDevicesPerUser, maxHistoryPage, maxMessageBytes, maxRecipients, maxTokenTtlSeconds, maxTtlSeconds, maxUnreadCount, maxWidgetPropsBytes, mcpApprovals, mcpGate, mcpHandler, mcpIcons, mcpInstructions, mcpRoutes, mcpServer, mcpToolContext, mcpTools, migrationCommand, newWebhookSecret, normalizeRoles, notFound, notificationSenders, notificationsMigration, oauthWellKnownPaths, objectKey, onDoNotDisturb, parseColumns, parseRoles, passwordLength, pluginOptions, pluginsBeforeSignIn, pluginsFor, pluginsVariable, pngIcons, poolWaitMs, postgresDeviceStore, postgresHistoryStore, processor, pushMessage, queryRunnerMigration, readDbConnections, readJson, readOnlyQueryTool, readOperatorSettings, readOutbox, readPlatformSignIn, readRecordTools, readVapidSettings, readWriteQueryTool, readableResources, realtimeLimits, realtimeRelayMigration, recordOf, refusalOf, refused, relayed, removeDevicesWithUsers, removeHistoryWithUsers, removeMember, renderVariant, requestApproval, requireEnv, resourceChange, roleChoices, runInBackground, runReadOnlyQuery, runReadWriteQuery, runTurn, runtimeUrl, runtimeVariable, s3FileStore, seesAssistant, seesRuntime, sendNotification, sessionOf, setMemberRoles, setUserBanned, setUserRole, shownAppName, signInPolicyMethods, socketMessageCapOf, sourceOptions, sourcesFor, sourcesVariable, sqlSends, startErrorReporting, startJobs, statusMigration, statusNames, storageCleanupSchedule, storageProvider, storeFile, storeUsage, streamChatCompletion, subscription, supabasePath, thumbHash, unauthorized, updateRecordTool, urgencies, vapidPrivateKeyVariable, vapidPublicKeyVariable, vapidSubjectVariable, variantPath, verifySignature, verifyStandardWebhook, waitUntil, wantsLongQueries, webManifestPath, webPushSender, webauthnPath, webhooksMigration, widget, withCacheBreakpoints, withTrustedApps, writeRecordTools };
package/dist/index.js CHANGED
@@ -25,7 +25,7 @@ import { apiKeySettings, apiKeyVariables } from "./api-keys/api-key-settings.js"
25
25
  import { apiKeyCaller } from "./api-keys/resolve-caller.js";
26
26
  import { jwtAuthenticator, jwtVerifier } from "./auth/jwt.js";
27
27
  import { realtimeLimits } from "./realtime/realtime-limits.js";
28
- import { relayed } from "./realtime/relay/realtime-options.js";
28
+ import { relayed, sqlSends } from "./realtime/relay/realtime-options.js";
29
29
  import { backgroundSettled, waitUntil } from "./functions/wait-until.js";
30
30
  import { objectKey } from "./storage/object-key.js";
31
31
  import { appNameOver, defaultAppName, shownAppName } from "./app-name.js";
@@ -78,6 +78,8 @@ import { readVapidSettings, vapidPrivateKeyVariable, vapidPublicKeyVariable, vap
78
78
  import { webPushSender } from "./notifications/web-push/web-push-sender.js";
79
79
  import { notificationSenders } from "./notifications/senders-of.js";
80
80
  import { NotificationSendError, maxMessageBytes, maxRecipients } from "./notifications/send-input.js";
81
+ import { onDoNotDisturb } from "./status/do-not-disturb.js";
82
+ import { statusNames } from "./status/options.js";
81
83
  import { errorReportingSettings, errorReportingVariable } from "./error-reporting/error-reporting-settings.js";
82
84
  import { storageCleanupSchedule } from "./files/cleanup-schedule.js";
83
85
  import { limitedFileStore, storeUsage } from "./files/limits.js";
@@ -109,9 +111,11 @@ import { checkResourcesOutsideAuthStore } from "./better-auth/store/resources-ou
109
111
  import { checkComputed, computedMigration } from "./computed/computed-schema.js";
110
112
  import { checkWebhooksSchema, webhooksMigration } from "./webhooks/table/schema.js";
111
113
  import { checkRealtimeRelay, realtimeRelayMigration } from "./realtime/relay/relay-schema.js";
114
+ import { channelSendsMigration, checkChannelSends } from "./channels/sends-schema.js";
112
115
  import { checkAi } from "./ai/check-ai.js";
113
116
  import { checkNotificationsSchema, notificationsMigration } from "./notifications/schema.js";
114
117
  import { sendNotification } from "./notifications/send-notification.js";
118
+ import { checkStatusSchema, statusMigration } from "./status/schema.js";
115
119
  import { maxWidgetPropsBytes, widget } from "./assistant/widget.js";
116
120
  import { checkQueryRunner, queryRunnerMigration } from "./assistant/query-runner/query-runner.js";
117
121
  import { startErrorReporting } from "./error-reporting/error-reporter.js";
@@ -127,4 +131,4 @@ import { enqueueJob } from "./jobs/declared-job.js";
127
131
  import { defineEmail } from "./mail/define-email.js";
128
132
  import { startJobs } from "./jobs/runtime.js";
129
133
  import { jobsDrainApp } from "./jobs/drain-route.js";
130
- export { ConnectionsBusy, DeviceStoreError, FatalJobError, HistoryStoreError, HttpProblem, MissingCredentials, ModelError, NotificationSendError, PushMessageError, addAppRoleToOwners, addCommentTool, addMember, apiKeyCaller, apiKeySettings, apiKeyVariables, appNameOver, aspectRatio, assistantProtocolRoutes, assistantRoles, assistantSettings, assistantVariables, authSchemaDrift, authSchemaMigration, authStoreTables, backgroundSettled, badRequest, batchWriteTool, beforeSignInPluginsVariable, betterAuthTokens, capSocketMessages, changesMigration, checkAi, checkAuthSchema, checkChanges, checkComments, checkComputed, checkJobsSchema, checkNotificationsSchema, checkQueryRunner, checkRealtimeRelay, checkResourcesOutsideAuthStore, checkSourceNames, checkViewQueries, checkWebhooksSchema, coalesce, commentTools, commentsMigration, computedMigration, configExports, consoleAuditQueue, conversationKey, countRecordsTool, createAdmin, createConversations, createEncryptionTools, createFileCleanup, createOrganization, createRecordTool, createUser, defaultAppName, defaultHistoryPage, defaultSignInPolicy, defineEmail, defineFunction, defineJob, defineTool, deleteRecordTool, deleteUser, describeResourceTool, deviceIdParam, deviceStore, devicesPath, enqueueJob, errorReportingSettings, errorReportingVariable, eventMatcher, fileConversationStore, fileSize, fileType, forbidden, formatUri, functionApp, generateVapidKeys, getRecordTool, goTruePath, handleDroppedConnections, hasUsers, historyIdParam, historyIdPattern, historyLimits, historyPath, historyStore, honorSocketMessageCaps, imageSize, isBackgroundWork, isCaptured, isJobDefinition, isOAuthWellKnownPath, isSchedule, issueToken, jobDefinitions, jobsDrainApp, jobsSchema, jobsSchemaMigration, jwtAuthenticator, jwtVerifier, limitedFileStore, listCommentsTool, listOrganizations, listRecordsTool, listResourcesTool, listUsers, localFileStore, longQueryTimeoutMs, mapSettings, mapStyleUrl, mapStyleVariable, maxDevicesPerUser, maxHistoryPage, maxMessageBytes, maxRecipients, maxTokenTtlSeconds, maxTtlSeconds, maxUnreadCount, maxWidgetPropsBytes, mcpApprovals, mcpGate, mcpHandler, mcpIcons, mcpInstructions, mcpRoutes, mcpServer, mcpToolContext, mcpTools, migrationCommand, newWebhookSecret, normalizeRoles, notFound, notificationSenders, notificationsMigration, oauthWellKnownPaths, objectKey, parseColumns, parseRoles, passwordLength, pluginOptions, pluginsBeforeSignIn, pluginsFor, pluginsVariable, pngIcons, poolWaitMs, postgresDeviceStore, postgresHistoryStore, processor, pushMessage, queryRunnerMigration, readDbConnections, readJson, readOnlyQueryTool, readOperatorSettings, readOutbox, readPlatformSignIn, readRecordTools, readVapidSettings, readWriteQueryTool, readableResources, realtimeLimits, realtimeRelayMigration, recordOf, refusalOf, refused, relayed, removeDevicesWithUsers, removeHistoryWithUsers, removeMember, renderVariant, requestApproval, requireEnv, resourceChange, roleChoices, runInBackground, runReadOnlyQuery, runReadWriteQuery, runTurn, runtimeUrl, runtimeVariable, s3FileStore, seesAssistant, seesRuntime, sendNotification, sessionOf, setMemberRoles, setUserBanned, setUserRole, shownAppName, signInPolicyMethods, socketMessageCapOf, sourceOptions, sourcesFor, sourcesVariable, startErrorReporting, startJobs, storageCleanupSchedule, storageProvider, storeFile, storeUsage, streamChatCompletion, subscription, supabasePath, thumbHash, unauthorized, updateRecordTool, urgencies, vapidPrivateKeyVariable, vapidPublicKeyVariable, vapidSubjectVariable, variantPath, verifySignature, verifyStandardWebhook, waitUntil, wantsLongQueries, webManifestPath, webPushSender, webauthnPath, webhooksMigration, widget, withCacheBreakpoints, withTrustedApps, writeRecordTools };
134
+ export { ConnectionsBusy, DeviceStoreError, FatalJobError, HistoryStoreError, HttpProblem, MissingCredentials, ModelError, NotificationSendError, PushMessageError, addAppRoleToOwners, addCommentTool, addMember, apiKeyCaller, apiKeySettings, apiKeyVariables, appNameOver, aspectRatio, assistantProtocolRoutes, assistantRoles, assistantSettings, assistantVariables, authSchemaDrift, authSchemaMigration, authStoreTables, backgroundSettled, badRequest, batchWriteTool, beforeSignInPluginsVariable, betterAuthTokens, capSocketMessages, changesMigration, channelSendsMigration, checkAi, checkAuthSchema, checkChanges, checkChannelSends, checkComments, checkComputed, checkJobsSchema, checkNotificationsSchema, checkQueryRunner, checkRealtimeRelay, checkResourcesOutsideAuthStore, checkSourceNames, checkStatusSchema, checkViewQueries, checkWebhooksSchema, coalesce, commentTools, commentsMigration, computedMigration, configExports, consoleAuditQueue, conversationKey, countRecordsTool, createAdmin, createConversations, createEncryptionTools, createFileCleanup, createOrganization, createRecordTool, createUser, defaultAppName, defaultHistoryPage, defaultSignInPolicy, defineEmail, defineFunction, defineJob, defineTool, deleteRecordTool, deleteUser, describeResourceTool, deviceIdParam, deviceStore, devicesPath, enqueueJob, errorReportingSettings, errorReportingVariable, eventMatcher, fileConversationStore, fileSize, fileType, forbidden, formatUri, functionApp, generateVapidKeys, getRecordTool, goTruePath, handleDroppedConnections, hasUsers, historyIdParam, historyIdPattern, historyLimits, historyPath, historyStore, honorSocketMessageCaps, imageSize, isBackgroundWork, isCaptured, isJobDefinition, isOAuthWellKnownPath, isSchedule, issueToken, jobDefinitions, jobsDrainApp, jobsSchema, jobsSchemaMigration, jwtAuthenticator, jwtVerifier, limitedFileStore, listCommentsTool, listOrganizations, listRecordsTool, listResourcesTool, listUsers, localFileStore, longQueryTimeoutMs, mapSettings, mapStyleUrl, mapStyleVariable, maxDevicesPerUser, maxHistoryPage, maxMessageBytes, maxRecipients, maxTokenTtlSeconds, maxTtlSeconds, maxUnreadCount, maxWidgetPropsBytes, mcpApprovals, mcpGate, mcpHandler, mcpIcons, mcpInstructions, mcpRoutes, mcpServer, mcpToolContext, mcpTools, migrationCommand, newWebhookSecret, normalizeRoles, notFound, notificationSenders, notificationsMigration, oauthWellKnownPaths, objectKey, onDoNotDisturb, parseColumns, parseRoles, passwordLength, pluginOptions, pluginsBeforeSignIn, pluginsFor, pluginsVariable, pngIcons, poolWaitMs, postgresDeviceStore, postgresHistoryStore, processor, pushMessage, queryRunnerMigration, readDbConnections, readJson, readOnlyQueryTool, readOperatorSettings, readOutbox, readPlatformSignIn, readRecordTools, readVapidSettings, readWriteQueryTool, readableResources, realtimeLimits, realtimeRelayMigration, recordOf, refusalOf, refused, relayed, removeDevicesWithUsers, removeHistoryWithUsers, removeMember, renderVariant, requestApproval, requireEnv, resourceChange, roleChoices, runInBackground, runReadOnlyQuery, runReadWriteQuery, runTurn, runtimeUrl, runtimeVariable, s3FileStore, seesAssistant, seesRuntime, sendNotification, sessionOf, setMemberRoles, setUserBanned, setUserRole, shownAppName, signInPolicyMethods, socketMessageCapOf, sourceOptions, sourcesFor, sourcesVariable, sqlSends, startErrorReporting, startJobs, statusMigration, statusNames, storageCleanupSchedule, storageProvider, storeFile, storeUsage, streamChatCompletion, subscription, supabasePath, thumbHash, unauthorized, updateRecordTool, urgencies, vapidPrivateKeyVariable, vapidPublicKeyVariable, vapidSubjectVariable, variantPath, verifySignature, verifyStandardWebhook, waitUntil, wantsLongQueries, webManifestPath, webPushSender, webauthnPath, webhooksMigration, widget, withCacheBreakpoints, withTrustedApps, writeRecordTools };
@@ -1,5 +1,6 @@
1
1
  import { checkNotificationsOptions } from "./notifications/options.js";
2
2
  import { checkCallsOptions } from "./calls/calls-options.js";
3
+ import { checkStatusOptions } from "./status/options.js";
3
4
  import { isSubscription } from "./webhooks/subscription.js";
4
5
  import { appPool, longQueryPool } from "./app-pool.js";
5
6
  import { AppAuthInput, appAuthOptions, buildAppAuth } from "./better-auth/auth-settings.js";
@@ -11,4 +12,4 @@ import { fileProviderOf } from "./files/provider-settings.js";
11
12
  import { declaredJob } from "./jobs/declared-job.js";
12
13
  import { AuthPlugins, buildAuth, createAuth, loadAuthPlugins } from "./better-auth/create-auth.js";
13
14
  import { webhooksModule } from "./webhooks/table/webhooks-module.js";
14
- export { type AppAuthInput, type AuthPlugins, appAuthOptions, appPool, betterAuthAuthenticator, buildAppAuth, buildAuth, buildAuthenticator, checkCallsOptions, checkNotificationsOptions, createAuth, declaredJob, fileProviderOf, isSubscription, loadAuthPlugins, localFiles, longQueryPool, s3Files, webhooksModule };
15
+ export { type AppAuthInput, type AuthPlugins, appAuthOptions, appPool, betterAuthAuthenticator, buildAppAuth, buildAuth, buildAuthenticator, checkCallsOptions, checkNotificationsOptions, checkStatusOptions, createAuth, declaredJob, fileProviderOf, isSubscription, loadAuthPlugins, localFiles, longQueryPool, s3Files, webhooksModule };
package/dist/internal.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { buildAuth, createAuth, loadAuthPlugins } from "./better-auth/create-auth.js";
2
2
  import { isSubscription } from "./webhooks/subscription.js";
3
3
  import { checkNotificationsOptions } from "./notifications/options.js";
4
+ import { checkStatusOptions } from "./status/options.js";
4
5
  import { localFiles } from "./files/local-files.js";
5
6
  import { s3Files } from "./files/s3-files.js";
6
7
  import { appPool, longQueryPool } from "./app-pool.js";
@@ -11,4 +12,4 @@ import { appAuthOptions, buildAppAuth } from "./better-auth/auth-settings.js";
11
12
  import { buildAuthenticator } from "./better-auth/authenticate-settings.js";
12
13
  import { webhooksModule } from "./webhooks/table/webhooks-module.js";
13
14
  import { checkCallsOptions } from "./calls/calls-options.js";
14
- export { appAuthOptions, appPool, betterAuthAuthenticator, buildAppAuth, buildAuth, buildAuthenticator, checkCallsOptions, checkNotificationsOptions, createAuth, declaredJob, fileProviderOf, isSubscription, loadAuthPlugins, localFiles, longQueryPool, s3Files, webhooksModule };
15
+ export { appAuthOptions, appPool, betterAuthAuthenticator, buildAppAuth, buildAuth, buildAuthenticator, checkCallsOptions, checkNotificationsOptions, checkStatusOptions, createAuth, declaredJob, fileProviderOf, isSubscription, loadAuthPlugins, localFiles, longQueryPool, s3Files, webhooksModule };
package/dist/meta.d.ts CHANGED
@@ -59,6 +59,8 @@ export type CallerMeta = {
59
59
  bell?: true;
60
60
  webPushKey?: string;
61
61
  };
62
+ /** With `status: {}`: people set their own status, and see the statuses of those they share an organization with. */
63
+ status?: true;
62
64
  /** The language the admin app's screens are in, when the app sets one for everyone. */
63
65
  locale?: Locale;
64
66
  /** The hosts besides the app's own whose images the admin app draws with the page's referrer, when there are any. */
package/dist/meta.js CHANGED
@@ -97,6 +97,7 @@ const createMeta = (deps, appName, appIcon) => {
97
97
  ...deps.bell && { bell: true },
98
98
  ...deps.webPushKey && { webPushKey: deps.webPushKey }
99
99
  } },
100
+ ...deps.status && { status: true },
100
101
  ...deps.locale && { locale: deps.locale },
101
102
  ...deps.files && deps.files.trustedHosts.length > 0 && { files: { trustedHosts: deps.files.trustedHosts } },
102
103
  ...plugins.length > 0 && { plugins },
@@ -5,8 +5,8 @@ import { forbidden } from "../problem.js";
5
5
  * Refused: visitors, outside apps acting for the user (an MCP client), and every session whose authenticator does not
6
6
  * mark it as the user's own sign-in (`Session.signIn`), staff signed in as the user included.
7
7
  */
8
- const ownSignInUser = (session) => {
9
- const refuse = () => forbidden("own-sign-in-only", "Only someone signed in as themselves can reach their devices and notifications");
8
+ const ownSignInUser = (session, what = "reach their devices and notifications") => {
9
+ const refuse = () => forbidden("own-sign-in-only", `Only someone signed in as themselves can ${what}`);
10
10
  if (session.anonymous || session.client || session.signIn?.staff !== false) throw refuse();
11
11
  const userId = String(session.user.id);
12
12
  if (userId === "" || session.user.id === void 0 || session.user.id === null) throw refuse();
@@ -16,7 +16,8 @@ const listed = (counts) => [...counts].map(([method, count]) => `${count} in ${m
16
16
  * One try of a send, resuming from `given`: for a user send, each user's history entry when the send is for the inbox
17
17
  * (once, as `record` marks it), then the user's devices as the store lists them now; for a device send, the devices
18
18
  * with those ids now. A device the store gives that is not the user's, or not one asked for, gets nothing. With
19
- * `liveSessions`, a device whose session no longer stands is removed instead of pushed to. Each kind's
19
+ * `liveSessions`, a device whose session no longer stands is removed instead of pushed to; with `quiet`, a device of a
20
+ * user on do not disturb gets nothing, now or on a later try, though the history keeps the entry. Each kind's
20
21
  * devices go to its sender in one batch; a kind without a sender is skipped. An answer of `gone` removes the device,
21
22
  * `retry` leaves it to the next try once and gives up on it the second time, and any other is done. A sender that
22
23
  * throws leaves its batch to the next try. The try then throws, counting what is left, so the job retries it, or
@@ -70,8 +71,19 @@ const deliver = async ({ to, message }, given, steps) => {
70
71
  }
71
72
  return pending.filter(stands);
72
73
  };
74
+ const undisturbed = async (devices) => {
75
+ const { quiet } = steps;
76
+ const pending = devices.filter((device) => !done.has(device.id));
77
+ if (!quiet || pending.length === 0) return pending;
78
+ const held = await quiet([...new Set(pending.map((device) => device.userId))]);
79
+ const kept = pending.filter((device) => !held.has(device.userId));
80
+ if (kept.length === pending.length) return kept;
81
+ for (const device of pending.filter((device) => held.has(device.userId))) markDone(device.id);
82
+ await steps.save(progress);
83
+ return kept;
84
+ };
73
85
  const push = async (given) => {
74
- const devices = await standing(given);
86
+ const devices = await undisturbed(await standing(given));
75
87
  for (const device of devices) if (!steps.senders.some((sender) => sender.kinds.includes(device.kind))) markDone(device.id);
76
88
  for (const sender of steps.senders) {
77
89
  const batch = devices.filter((device) => sender.kinds.includes(device.kind) && !done.has(device.id));
@@ -26,11 +26,12 @@ const recordOnce = (db, options, job) => async (userId, entry, progress) => {
26
26
  };
27
27
  /**
28
28
  * The job that delivers what `sendNotification` sends, through `senders` (guarded, see `notificationSenders`) and the
29
- * stores `options` configure; with `liveSessions`, as with Protobase's auth, only to devices whose session stands. A
29
+ * stores `options` configure; with `liveSessions`, as with Protobase's auth, only to devices whose session stands, and
30
+ * with `quiet`, as with `status`, to no device of a user on do not disturb. A
30
31
  * try resumes where the last one stopped (see `deliver`); once delivered, the job keeps no message. A few tries only: a
31
32
  * device that keeps asking for a retry is given up on the second time.
32
33
  */
33
- const pushJob = (options, senders, liveSessions) => defineJob({
34
+ const pushJob = (options, senders, liveSessions, quiet) => defineJob({
34
35
  name: pushJobName,
35
36
  payload: pushPayload,
36
37
  maxAttempts: 5,
@@ -44,7 +45,8 @@ const pushJob = (options, senders, liveSessions) => defineJob({
44
45
  save: (progress) => saveJobProgress(db, job, progress),
45
46
  hold: () => holdJob(db, job),
46
47
  complete: () => replaceJobData(db, job, { complete: true }),
47
- ...liveSessions && { liveSessions }
48
+ ...liveSessions && { liveSessions },
49
+ ...quiet && { quiet: (userIds) => quiet(db, userIds) }
48
50
  });
49
51
  }
50
52
  });
@@ -1,7 +1,7 @@
1
1
  import { migrationCommand } from "../changes/capture-schema.js";
2
2
  import { historyNames, notificationsNames } from "./options.js";
3
- import { storeSchema } from "../better-auth/store/store-names.js";
4
3
  import { deviceSessionColumnSql, devicesTableSql, qualified } from "./schema-sql.js";
4
+ import { storeSchema } from "../better-auth/store/store-names.js";
5
5
  import { historyTableSql } from "./history-sql.js";
6
6
  import { sql } from "kysely";
7
7
  //#region src/notifications/schema.ts
@@ -11,9 +11,12 @@ const authUsersTable = (auth) => ({
11
11
  table: auth.options.user?.modelName ?? "user"
12
12
  });
13
13
  const regclass = async (db, name) => (await sql`select to_regclass(${name})::oid as oid`.execute(db)).rows[0].oid;
14
- const userIdType = async (db, users) => {
14
+ const userIdType = async (db, users, of = {
15
+ feature: "notifications",
16
+ rows: "devices"
17
+ }) => {
15
18
  const oid = await regclass(db, qualified(users));
16
- if (oid === null) throw new Error(`notifications: auth's users table ${qualified(users)} is not in the app's database, so devices cannot point at it`);
19
+ if (oid === null) throw new Error(`${of.feature}: auth's users table ${qualified(users)} is not in the app's database, so ${of.rows} cannot point at it`);
17
20
  const { rows } = await sql`select format_type(atttypid, atttypmod) as type from pg_attribute where attrelid = ${oid} and attname = 'id'`.execute(db);
18
21
  return rows[0].type;
19
22
  };
@@ -89,4 +92,4 @@ const checkNotificationsSchema = async (db, options = {}, auth) => {
89
92
  await checkHistoryTable(db, options, users);
90
93
  };
91
94
  //#endregion
92
- export { authUsersTable, checkNotificationsSchema, notificationsMigration };
95
+ export { authUsersTable, checkNotificationsSchema, notificationsMigration, regclass, userIdType };
package/dist/package.js CHANGED
@@ -1,4 +1,4 @@
1
1
  //#region package.json
2
- var version = "0.59.1";
2
+ var version = "0.59.3";
3
3
  //#endregion
4
4
  export { version };
@@ -1,5 +1,6 @@
1
1
  import { writeTransaction } from "../transactions.js";
2
2
  import { fitsFrame } from "./broadcast-frame.js";
3
+ import { followedPoll } from "./followed-poll.js";
3
4
  import { realtimeLimits } from "./realtime-limits.js";
4
5
  import { sql } from "kysely";
5
6
  import { Buffer } from "node:buffer";
@@ -43,8 +44,6 @@ const messagesTableExists = async (db) => (await sql`select to_regclass('realtim
43
44
  * `realtime.messages` sends nothing, and is checked again at each read.
44
45
  */
45
46
  const databaseBroadcasts = ({ db, statementTimeoutMs, broadcasts, intervalMs = 1e3, report }) => {
46
- let followers = 0;
47
- let running = false;
48
47
  const read = async () => {
49
48
  if (!await messagesTableExists(db)) return;
50
49
  for (;;) {
@@ -56,29 +55,7 @@ const databaseBroadcasts = ({ db, statementTimeoutMs, broadcasts, intervalMs = 1
56
55
  if (rows.length < batchSize) return;
57
56
  }
58
57
  };
59
- const tick = async () => {
60
- if (followers === 0) {
61
- running = false;
62
- return;
63
- }
64
- await read().catch(report);
65
- setTimeout(() => void tick(), intervalMs);
66
- };
67
- return {
68
- /** Reads the table from now on, until the returned function is called; once for each call, however often. */
69
- follow: () => {
70
- followers += 1;
71
- if (!running) {
72
- running = true;
73
- tick();
74
- }
75
- let followed = true;
76
- return () => {
77
- if (!followed) return;
78
- followed = false;
79
- followers -= 1;
80
- };
81
- } };
58
+ return followedPoll(read, intervalMs, report);
82
59
  };
83
60
  //#endregion
84
61
  export { databaseBroadcasts, messagesTableExists, publicationOfRow };
@@ -0,0 +1,34 @@
1
+ //#region src/realtime/followed-poll.ts
2
+ /**
3
+ * Runs `read` every `intervalMs` while anyone follows, starting at once with the first follower; a failed read goes to
4
+ * `report`, and the next one tries again.
5
+ */
6
+ const followedPoll = (read, intervalMs, report) => {
7
+ let followers = 0;
8
+ let running = false;
9
+ const tick = async () => {
10
+ if (followers === 0) {
11
+ running = false;
12
+ return;
13
+ }
14
+ await read().catch(report);
15
+ setTimeout(() => void tick(), intervalMs);
16
+ };
17
+ return {
18
+ /** Reads from now on, until the returned function is called; once for each call, however often. */
19
+ follow: () => {
20
+ followers += 1;
21
+ if (!running) {
22
+ running = true;
23
+ tick();
24
+ }
25
+ let followed = true;
26
+ return () => {
27
+ if (!followed) return;
28
+ followed = false;
29
+ followers -= 1;
30
+ };
31
+ } };
32
+ };
33
+ //#endregion
34
+ export { followedPoll };
@@ -5,15 +5,17 @@ export type RealtimePruneOptions = {
5
5
  statementTimeoutMs: number;
6
6
  /** Whether the app's processes relay through `realtime_relay`, whose frames and counts are pruned too. */
7
7
  relayed: boolean;
8
+ /** Whether SQL sends to the record channels through `channels.sends`, whose old rows are pruned too. */
9
+ sqlSends: boolean;
8
10
  /** Where a failed prune goes; the next one tries again. */
9
11
  report: (error: unknown) => void;
10
12
  };
11
13
  /**
12
14
  * Prunes what the realtime tables keep whether or not a socket is open, as on an app that never opens one: the old
13
- * broadcast rows of `realtime.messages` and, relayed, the relay's old frames and counts, on `pruneSchedule`. It runs
14
- * only while the process does, so it never wakes a sleeping app.
15
+ * broadcast rows of `realtime.messages`, relayed, the relay's old frames and counts and, with `sqlSends`, the old
16
+ * rows of `channels.sends`, on `pruneSchedule`. It runs only while the process does, so it never wakes a sleeping app.
15
17
  */
16
- export declare const startRealtimePrune: ({ db, statementTimeoutMs, relayed, report }: RealtimePruneOptions) => {
18
+ export declare const startRealtimePrune: ({ db, statementTimeoutMs, relayed, sqlSends, report }: RealtimePruneOptions) => {
17
19
  stop: () => Promise<void>;
18
20
  };
19
21
  export type RealtimePrune = ReturnType<typeof startRealtimePrune>;
@@ -1,12 +1,17 @@
1
1
  import { pruneFrames } from "./relay/relay-frame-sql.js";
2
2
  import { pruneBroadcasts } from "./broadcast-prune.js";
3
+ import { pruneSends } from "../channels/sends-prune.js";
3
4
  import { pruneSchedule } from "./prune-schedule.js";
4
5
  //#region src/realtime/realtime-prune.ts
5
6
  /**
6
7
  * Prunes what the realtime tables keep whether or not a socket is open, as on an app that never opens one: the old
7
- * broadcast rows of `realtime.messages` and, relayed, the relay's old frames and counts, on `pruneSchedule`. It runs
8
- * only while the process does, so it never wakes a sleeping app.
8
+ * broadcast rows of `realtime.messages`, relayed, the relay's old frames and counts and, with `sqlSends`, the old
9
+ * rows of `channels.sends`, on `pruneSchedule`. It runs only while the process does, so it never wakes a sleeping app.
9
10
  */
10
- const startRealtimePrune = ({ db, statementTimeoutMs, relayed, report }) => pruneSchedule([() => pruneBroadcasts(db, statementTimeoutMs), ...relayed ? [() => pruneFrames(db)] : []], report);
11
+ const startRealtimePrune = ({ db, statementTimeoutMs, relayed, sqlSends, report }) => pruneSchedule([
12
+ () => pruneBroadcasts(db, statementTimeoutMs),
13
+ ...relayed ? [() => pruneFrames(db)] : [],
14
+ ...sqlSends ? [() => pruneSends(db, statementTimeoutMs)] : []
15
+ ], report);
11
16
  //#endregion
12
17
  export { startRealtimePrune };
@@ -7,7 +7,14 @@ export type RealtimeOptions = {
7
7
  * `realtime_relay` schema of the app's database, which `protobase migration` writes.
8
8
  */
9
9
  processes?: 'one' | 'many';
10
+ /**
11
+ * `true` lets SQL in the app's database broadcast to the record channels with `channels.send()`, from the `channels`
12
+ * schema `protobase migration` writes.
13
+ */
14
+ sqlSends?: boolean;
10
15
  };
11
16
  /** Whether the options share broadcasts and presence between processes. */
12
17
  export declare const relayed: (options?: RealtimeOptions) => boolean;
18
+ /** Whether the options let SQL broadcast to the record channels. */
19
+ export declare const sqlSends: (options?: RealtimeOptions) => boolean;
13
20
  //#endregion
@@ -5,5 +5,11 @@ const relayed = (options = {}) => {
5
5
  if (options.processes === "many") return true;
6
6
  throw new Error(`Invalid realtime.processes "${String(options.processes)}": use "one" or "many"`);
7
7
  };
8
+ /** Whether the options let SQL broadcast to the record channels. */
9
+ const sqlSends = (options = {}) => {
10
+ if (options.sqlSends === void 0) return false;
11
+ if (typeof options.sqlSends === "boolean") return options.sqlSends;
12
+ throw new Error(`Invalid realtime.sqlSends "${String(options.sqlSends)}": use true or false`);
13
+ };
8
14
  //#endregion
9
- export { relayed };
15
+ export { relayed, sqlSends };
@@ -0,0 +1,9 @@
1
+ import { StatusNames } from "./options.js";
2
+ import { Db } from "@protobase/query";
3
+ //#region src/status/do-not-disturb.d.ts
4
+ /**
5
+ * Which of these people are on do not disturb now: their status says so and its clear time, if any, is still to come.
6
+ * The one check of everything that would alert someone, such as a push, so each holds it alike.
7
+ */
8
+ export declare const onDoNotDisturb: (db: Db, names: StatusNames, userIds: readonly string[]) => Promise<Set<string>>;
9
+ //#endregion
@@ -0,0 +1,15 @@
1
+ import { qualified } from "../notifications/schema-sql.js";
2
+ import { sql } from "kysely";
3
+ //#region src/status/do-not-disturb.ts
4
+ /**
5
+ * Which of these people are on do not disturb now: their status says so and its clear time, if any, is still to come.
6
+ * The one check of everything that would alert someone, such as a push, so each holds it alike.
7
+ */
8
+ const onDoNotDisturb = async (db, names, userIds) => {
9
+ if (userIds.length === 0) return /* @__PURE__ */ new Set();
10
+ const { rows } = await sql`select user_id from ${sql.raw(qualified(names))}
11
+ where user_id in (${sql.join([...new Set(userIds)])}) and availability = 'do_not_disturb' and (clears_at is null or clears_at > now())`.execute(db);
12
+ return new Set(rows.map((row) => String(row.user_id)));
13
+ };
14
+ //#endregion
15
+ export { onDoNotDisturb };
@@ -0,0 +1,15 @@
1
+ //#region src/status/options.d.ts
2
+ /** People's own status, turned on with `status: {}` in `protobase.config.ts`; it needs auth with organizations. */
3
+ export type StatusOptions = {
4
+ /** The schema of the statuses table; default `status`. */
5
+ schema?: string;
6
+ };
7
+ /** The statuses table's names, checked: a bad schema name is an error naming the option. */
8
+ export declare const statusNames: (options?: StatusOptions) => {
9
+ schema: string;
10
+ table: string;
11
+ };
12
+ export type StatusNames = ReturnType<typeof statusNames>;
13
+ /** The options, checked as `status` takes them. */
14
+ export declare const checkStatusOptions: (options: StatusOptions) => StatusOptions;
15
+ //#endregion
@@ -0,0 +1,25 @@
1
+ //#region src/status/options.ts
2
+ const namePattern = /^[a-z_][a-z0-9_]*$/;
3
+ const maxNameBytes = 63;
4
+ /** The statuses table's names, checked: a bad schema name is an error naming the option. */
5
+ const statusNames = (options = {}) => {
6
+ const schema = options.schema ?? "status";
7
+ if (typeof schema !== "string" || !namePattern.test(schema) || Buffer.byteLength(schema) > maxNameBytes) throw new Error(`status schema is not a valid name: use lowercase letters, digits and "_", at most ${maxNameBytes} characters`);
8
+ return {
9
+ schema,
10
+ table: "statuses"
11
+ };
12
+ };
13
+ /** The options, checked as `status` takes them. */
14
+ const checkStatusOptions = (options) => {
15
+ const unknown = Object.keys(options).find((key) => key !== "schema");
16
+ if (unknown !== void 0) throw new Error(`status takes no option "${unknown}": only schema`);
17
+ statusNames(options);
18
+ return options;
19
+ };
20
+ /** Status shows people to the members of their organizations, so an app without organizations cannot turn it on. */
21
+ const checkStatusAuth = (auth) => {
22
+ if (!auth?.organizations) throw new Error("status: {} shows people to the members of their organizations: turn organizations on in the auth config, or remove status");
23
+ };
24
+ //#endregion
25
+ export { checkStatusAuth, checkStatusOptions, statusNames };
@@ -0,0 +1,38 @@
1
+ import { ownSignInUser } from "../notifications/caller.js";
2
+ import { jsonBody, jsonOnly, limitedBody } from "../notifications/history-request.js";
3
+ import { statusInput } from "./status-input.js";
4
+ import { clearStatus, ownStatus, setStatus, visibleStatuses } from "./status-store.js";
5
+ import { Hono } from "hono";
6
+ //#region src/status/routes.ts
7
+ const noStore = { "Cache-Control": "no-store" };
8
+ const viewerOf = (session) => session.anonymous || session.client ? void 0 : String(session.user.id);
9
+ /**
10
+ * The status endpoints, mounted at `statusPath` behind the API's authenticator: `GET /` the caller's own status,
11
+ * `PUT /` sets it, `DELETE /` clears it, and `GET /people` the statuses of everyone who shares an organization with
12
+ * the caller. No request names whose status it sets: the person comes from the session alone, and only someone
13
+ * signed in as themselves sets theirs, never staff signed in as them (see `ownSignInUser`).
14
+ */
15
+ const statusRoutes = (db, tables) => {
16
+ const app = new Hono();
17
+ app.get("/", async (c) => {
18
+ const viewer = viewerOf(c.get("session"));
19
+ const status = viewer === void 0 ? void 0 : await ownStatus(db, tables, viewer);
20
+ return c.json({ status: status ?? null }, 200, noStore);
21
+ });
22
+ app.put("/", jsonOnly, limitedBody, async (c) => {
23
+ const userId = ownSignInUser(c.get("session"), "set their status");
24
+ await setStatus(db, tables, userId, statusInput(await jsonBody(c)));
25
+ return c.json({ status: await ownStatus(db, tables, userId) ?? null }, 200, noStore);
26
+ });
27
+ app.delete("/", async (c) => {
28
+ await clearStatus(db, tables, ownSignInUser(c.get("session"), "set their status"));
29
+ return c.body(null, 204);
30
+ });
31
+ app.get("/people", async (c) => {
32
+ const viewer = viewerOf(c.get("session"));
33
+ return c.json({ statuses: viewer === void 0 ? [] : await visibleStatuses(db, tables, viewer) }, 200, noStore);
34
+ });
35
+ return app;
36
+ };
37
+ //#endregion
38
+ export { statusRoutes };
@@ -0,0 +1,19 @@
1
+ import { quoteIdent } from "../changes/capture-sql.js";
2
+ import { qualified } from "../notifications/schema-sql.js";
3
+ //#region src/status/schema-sql.ts
4
+ /**
5
+ * The statuses table: one row per person who set a status, which goes with the user. A custom text or a clear time
6
+ * not set is NULL, and nothing has a default. Clearing a status deletes its row.
7
+ */
8
+ const statusesTableSql = ({ schema, table }, user) => [`create schema if not exists ${quoteIdent(schema)}`, `create table ${qualified({
9
+ schema,
10
+ table
11
+ })} (
12
+ user_id ${user.type} primary key references ${user.references} on delete cascade,
13
+ availability text not null check (availability in ('automatic', 'busy', 'do_not_disturb')),
14
+ text text check (char_length(text) between 1 and 100),
15
+ clears_at timestamptz,
16
+ updated_at timestamptz not null
17
+ )`];
18
+ //#endregion
19
+ export { statusesTableSql };
@@ -0,0 +1,12 @@
1
+ import { StatusOptions } from "./options.js";
2
+ import { AdminAuth } from "../better-auth/create-auth.js";
3
+ import { Db } from "@protobase/query";
4
+ //#region src/status/schema.d.ts
5
+ /**
6
+ * The SQL that adds the statuses table `status` keeps people's statuses in, its user column pointing at auth's users
7
+ * table; `undefined` when it exists. An app without organizations is refused (see `checkStatusAuth`).
8
+ */
9
+ export declare const statusMigration: (db: Db, options: StatusOptions, auth: AdminAuth | undefined) => Promise<string | undefined>;
10
+ /** Refuses to start while the statuses table is missing, or the app has no organizations. */
11
+ export declare const checkStatusSchema: (db: Db, options: StatusOptions, auth: AdminAuth | undefined) => Promise<void>;
12
+ //#endregion
@@ -0,0 +1,33 @@
1
+ import { migrationCommand } from "../changes/capture-schema.js";
2
+ import { qualified } from "../notifications/schema-sql.js";
3
+ import { checkStatusAuth, statusNames } from "./options.js";
4
+ import { authUsersTable, regclass, userIdType } from "../notifications/schema.js";
5
+ import { statusesTableSql } from "./schema-sql.js";
6
+ //#region src/status/schema.ts
7
+ /**
8
+ * The SQL that adds the statuses table `status` keeps people's statuses in, its user column pointing at auth's users
9
+ * table; `undefined` when it exists. An app without organizations is refused (see `checkStatusAuth`).
10
+ */
11
+ const statusMigration = async (db, options, auth) => {
12
+ checkStatusAuth(auth);
13
+ const names = statusNames(options);
14
+ if (await regclass(db, qualified(names)) !== null) return;
15
+ const users = authUsersTable(auth);
16
+ const type = await userIdType(db, users, {
17
+ feature: "status",
18
+ rows: "statuses"
19
+ });
20
+ return `${statusesTableSql(names, {
21
+ type,
22
+ references: `${qualified(users)} (id)`
23
+ }).join(";\n\n")};`;
24
+ };
25
+ /** Refuses to start while the statuses table is missing, or the app has no organizations. */
26
+ const checkStatusSchema = async (db, options, auth) => {
27
+ checkStatusAuth(auth);
28
+ const table = qualified(statusNames(options));
29
+ if (await regclass(db, table) !== null) return;
30
+ throw new Error(`The statuses table ${table} of status is missing. Write it as a migration with \`${migrationCommand}\`, apply it with the project's migrations, then start again.`);
31
+ };
32
+ //#endregion
33
+ export { checkStatusSchema, statusMigration };