@protobase/server 0.67.7 → 0.67.9

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.
@@ -6,4 +6,5 @@ import { maxTextLength } from "./user-tables/definition-resource.js";
6
6
  import { userTableResources } from "./user-tables/user-table-resources.js";
7
7
  import { builtInResources } from "./built-in-resources.js";
8
8
  import { webhooksModule } from "./webhooks/table/webhooks-module.js";
9
- export { type CheckedDefinition, builtInResources, configExports, definitionsSchema, maxTextLength, ownedTableNames, userTableResources, userTablesSchema, webhooksModule };
9
+ import { workflowsModule } from "./workflows/workflows-module.js";
10
+ export { type CheckedDefinition, builtInResources, configExports, definitionsSchema, maxTextLength, ownedTableNames, userTableResources, userTablesSchema, webhooksModule, workflowsModule };
@@ -5,4 +5,5 @@ import { configExports } from "./resource-source.js";
5
5
  import { ownedTableNames } from "./user-tables/owned-definitions.js";
6
6
  import { userTableResources } from "./user-tables/user-table-resources.js";
7
7
  import { webhooksModule } from "./webhooks/table/webhooks-module.js";
8
- export { builtInResources, configExports, definitionsSchema, maxTextLength, ownedTableNames, userTableResources, userTablesSchema, webhooksModule };
8
+ import { workflowsModule } from "./workflows/workflows-module.js";
9
+ export { builtInResources, configExports, definitionsSchema, maxTextLength, ownedTableNames, userTableResources, userTablesSchema, webhooksModule, workflowsModule };
@@ -8,6 +8,7 @@ import { Admin, Authenticator, PageSource, ViewSource } from "./types.js";
8
8
  import { CommentUsers } from "./comments/comment-users.js";
9
9
  import { HistoryOptions } from "./history/options.js";
10
10
  import { WebhooksOptions } from "./webhooks/table/options.js";
11
+ import { WorkflowsOptions } from "./workflows/options.js";
11
12
  import { NotificationsOptions } from "./notifications/options.js";
12
13
  import { CallsOptions } from "./calls/calls-options.js";
13
14
  import { StatusOptions } from "./status/options.js";
@@ -60,6 +61,8 @@ export type CreateAdminInput = {
60
61
  subscriptions?: SubscriptionDefinition[];
61
62
  /** Webhook subscriptions made in the admin app, from `webhooks: {}` (see `WebhooksOptions`). */
62
63
  webhooks?: WebhooksOptions;
64
+ /** Workflows made in the admin app and run on the app's jobs, from `workflows: {}` (see `WorkflowsOptions`). */
65
+ workflows?: WorkflowsOptions;
63
66
  /** How `/api/sb/realtime/v1` and `/api/channels` are served: by one process or several, and whether SQL sends to channels (see `RealtimeOptions`). */
64
67
  realtime?: RealtimeOptions;
65
68
  /** Each user's devices and the endpoints that register them, from `notifications: {}` (see `NotificationsOptions`). */
@@ -107,5 +110,5 @@ export type CreateAdminInput = {
107
110
  upgradeWebSocket?: UpgradeWebSocket;
108
111
  };
109
112
  /** The admin REST API as a Hono app. It uses only Web APIs, so it runs on Node, Workers and anywhere else Hono does. */
110
- export declare const createAdmin: ({ resources: appResources, views: appViews, userTables, pages, userMenu, bottomBars, db, authenticate, auth, functions, secret, files: filesOptions, events, history: historyOptions, jobs, subscriptions, webhooks: webhooksOptions, realtime, notifications, status, calls, mobile, app: identity, icons, bundlePlugins, fieldTypes, options, formsDifficulty, commentUsers: givenCommentUsers, verifyToken, upgradeWebSocket }: CreateAdminInput) => Admin;
113
+ export declare const createAdmin: ({ resources: appResources, views: appViews, userTables, pages, userMenu, bottomBars, db, authenticate, auth, functions, secret, files: filesOptions, events, history: historyOptions, jobs, subscriptions, webhooks: webhooksOptions, workflows: workflowsOptions, realtime, notifications, status, calls, mobile, app: identity, icons, bundlePlugins, fieldTypes, options, formsDifficulty, commentUsers: givenCommentUsers, verifyToken, upgradeWebSocket }: CreateAdminInput) => Admin;
111
114
  //#endregion
@@ -57,6 +57,7 @@ import { toApiFunction } from "./functions/define-function.js";
57
57
  import { createWebhooks } from "./webhooks/webhooks.js";
58
58
  import { webhookTable } from "./webhooks/table/webhook-table.js";
59
59
  import { subscriptionUrlCheck, subscriptionWriteCheck } from "./webhooks/table/validate-hook.js";
60
+ import { workflowEngine } from "./workflows/workflow-engine.js";
60
61
  import { callsService } from "./calls/calls-service.js";
61
62
  import { ringRoutes } from "./calls/ring-routes.js";
62
63
  import { upgradeAuthenticator } from "./functions/upgrade-caller.js";
@@ -124,7 +125,7 @@ const declaredRoles = (auth, roles) => {
124
125
  return names.length > 0 ? [...new Set(names)] : void 0;
125
126
  };
126
127
  /** The admin REST API as a Hono app. It uses only Web APIs, so it runs on Node, Workers and anywhere else Hono does. */
127
- const createAdmin = ({ resources: appResources, views: appViews = [], userTables, pages = [], userMenu, bottomBars = [], db, authenticate, auth, functions = {}, secret, files: filesOptions, events, history: historyOptions, jobs = [], subscriptions = [], webhooks: webhooksOptions, realtime, notifications, status, calls, mobile, app: identity, icons, bundlePlugins = [], fieldTypes = {}, options = {}, formsDifficulty, commentUsers: givenCommentUsers, verifyToken, upgradeWebSocket }) => {
128
+ const createAdmin = ({ resources: appResources, views: appViews = [], userTables, pages = [], userMenu, bottomBars = [], db, authenticate, auth, functions = {}, secret, files: filesOptions, events, history: historyOptions, jobs = [], subscriptions = [], webhooks: webhooksOptions, workflows: workflowsOptions, realtime, notifications, status, calls, mobile, app: identity, icons, bundlePlugins = [], fieldTypes = {}, options = {}, formsDifficulty, commentUsers: givenCommentUsers, verifyToken, upgradeWebSocket }) => {
128
129
  const basePath = options.basePath ?? "/api/v1";
129
130
  const appModels = appResources.map((resource) => resource.toModel());
130
131
  const withComments = appModels.some((model) => model.comments);
@@ -223,6 +224,7 @@ const createAdmin = ({ resources: appResources, views: appViews = [], userTables
223
224
  storage: servesKeys
224
225
  });
225
226
  const table = webhooksOptions && webhookTable(webhooksOptions, auth);
227
+ const engine = workflowsOptions && workflowEngine(workflowsOptions, () => deps, auth);
226
228
  const withAi = withAiFields || hasAiActions(viewModels);
227
229
  const model = withAi ? aiModel(options.assistant, globalThis.process?.env ?? {}) : void 0;
228
230
  const fill = withAiFields ? fillJob(() => deps) : void 0;
@@ -242,9 +244,10 @@ const createAdmin = ({ resources: appResources, views: appViews = [], userTables
242
244
  ...options.writeHooks ?? [],
243
245
  ...served.hooks,
244
246
  ...table ? [subscriptionWriteCheck(registry.entries.map((entry) => entry.model))] : [],
247
+ ...engine?.hooks ?? [],
245
248
  ...fill ? [fillHook(() => deps, fill)] : []
246
249
  ],
247
- writeChecks: table?.checkUrl ? [subscriptionUrlCheck(table.checkUrl)] : [],
250
+ writeChecks: [...table?.checkUrl ? [subscriptionUrlCheck(table.checkUrl)] : [], ...engine?.writeChecks ?? []],
248
251
  audit: options.audit ?? consoleAuditQueue(),
249
252
  report: options.onUnhandledError ?? ((error) => console.error(error)),
250
253
  accessOptions: {
@@ -572,6 +575,7 @@ const createAdmin = ({ resources: appResources, views: appViews = [], userTables
572
575
  metaPath: `${deps.systemPath}/meta`,
573
576
  jobs: [
574
577
  ...webhooks.jobs,
578
+ ...engine ? [engine.job] : [],
575
579
  ...withHistory && days !== void 0 ? [pruneHistoryJob(changes, days)] : [],
576
580
  ...mentions ? [mentions] : [],
577
581
  ...fill ? [fill] : [],
package/dist/index.d.ts CHANGED
@@ -16,6 +16,7 @@ import { Admin, AuditEvent, AuditQueue, Authenticator, Membership, PipelineHook,
16
16
  import { CommentUsers } from "./comments/comment-users.js";
17
17
  import { HistoryOptions } from "./history/options.js";
18
18
  import { WebhooksOptions } from "./webhooks/table/options.js";
19
+ import { WorkflowsOptions } from "./workflows/options.js";
19
20
  import { ApnsEnvironment, Device, DeviceKind, NewDevice } from "./notifications/device.js";
20
21
  import { DeviceStore } from "./notifications/device-store.js";
21
22
  import { HistoryStore, maxHistoryPage, maxUnreadCount } from "./notifications/history-store.js";
@@ -101,6 +102,8 @@ import { eventMatcher } from "./changes/match-events.js";
101
102
  import { parseColumns, readOutbox } from "./changes/outbox.js";
102
103
  import { newWebhookSecret } from "./webhooks/standard-webhooks.js";
103
104
  import { checkWebhooksSchema, webhooksMigration } from "./webhooks/table/schema.js";
105
+ import { checkWorkflowsSchema, workflowsMigration } from "./workflows/schema.js";
106
+ import { StartWorkflowOptions, startWorkflow } from "./workflows/start-workflow.js";
104
107
  import { checkUserTablesSchema, userTablesMigration } from "./user-tables/schema.js";
105
108
  import { ownedDefinitionsMigration } from "./user-tables/owned-definitions.js";
106
109
  import { UserTables, userTableResources } from "./user-tables/user-table-resources.js";
@@ -189,4 +192,4 @@ import { DrainResult, JobsRuntime, StartJobsOptions, startJobs } from "./jobs/ru
189
192
  import { jobsDrainApp } from "./jobs/drain-route.js";
190
193
  import { checkJobsSchema, jobsSchema, jobsSchemaMigration } from "./jobs/schema.js";
191
194
  import { AdminAuth, CreateAuthOptions, UserCreatedHook, normalizeRoles } from "./better-auth/create-auth.js";
192
- 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 MailAttachment, type MailMessage, MailServerTimeoutError, 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 PdfComponent, type PdfOptions, type PdfTemplate, PersonalMailError, type PersonalMailErrorCode, type PersonalMailProviderId, 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 UserTables, 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, callsMigration, capSocketMessages, changesMigration, channelSendsMigration, checkAi, checkAuthSchema, checkCalls, checkChanges, checkChannelSends, checkComments, checkComputed, checkJobsSchema, checkNotificationsSchema, checkQueryRunner, checkRealtimeRelay, checkResourcesOutsideAuthStore, checkSourceNames, checkStatusSchema, checkUserTablesSchema, checkViewQueries, checkWebhooksSchema, coalesce, commentTools, commentsMigration, computedMigration, configExports, consoleAuditQueue, conversationKey, countRecordsTool, createAdmin, createConversations, createEncryptionTools, createFileCleanup, createOrganization, createRecordTool, createUser, defaultAppName, defaultHistoryPage, defaultSignInPolicy, defineEmail, defineFunction, defineJob, definePdf, 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, ownedDefinitionsMigration, parseColumns, parseRoles, passwordLength, pluginOptions, pluginsBeforeSignIn, pluginsFor, pluginsVariable, pngIcons, poolWaitMs, postgresDeviceStore, postgresHistoryStore, processor, pushMessage, queryRunnerMigration, readDbConnections, readDefinitionsVersion, readJson, readOnlyQueryTool, readOperatorSettings, readOutbox, readPlatformSignIn, readRecordTools, readVapidSettings, readWriteQueryTool, readableResources, realtimeLimits, realtimeRelayMigration, recordOf, refusalOf, refused, relayed, reloadingAdmin, 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, userTableResources, userTablesMigration, vapidPrivateKeyVariable, vapidPublicKeyVariable, vapidSubjectVariable, variantPath, verifySignature, verifyStandardWebhook, waitUntil, wantsLongQueries, webManifestPath, webPushSender, webauthnPath, webhooksMigration, widget, withCacheBreakpoints, withTrustedApps, writeRecordTools };
195
+ 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 MailAttachment, type MailMessage, MailServerTimeoutError, 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 PdfComponent, type PdfOptions, type PdfTemplate, PersonalMailError, type PersonalMailErrorCode, type PersonalMailProviderId, 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 StartWorkflowOptions, 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 UserTables, type VapidSettings, type ViewSource, type WebPushFields, type WebPushOptions, type WebhooksOptions, type WidgetOptions, type WorkflowsOptions, 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, callsMigration, capSocketMessages, changesMigration, channelSendsMigration, checkAi, checkAuthSchema, checkCalls, checkChanges, checkChannelSends, checkComments, checkComputed, checkJobsSchema, checkNotificationsSchema, checkQueryRunner, checkRealtimeRelay, checkResourcesOutsideAuthStore, checkSourceNames, checkStatusSchema, checkUserTablesSchema, checkViewQueries, checkWebhooksSchema, checkWorkflowsSchema, coalesce, commentTools, commentsMigration, computedMigration, configExports, consoleAuditQueue, conversationKey, countRecordsTool, createAdmin, createConversations, createEncryptionTools, createFileCleanup, createOrganization, createRecordTool, createUser, defaultAppName, defaultHistoryPage, defaultSignInPolicy, defineEmail, defineFunction, defineJob, definePdf, 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, ownedDefinitionsMigration, parseColumns, parseRoles, passwordLength, pluginOptions, pluginsBeforeSignIn, pluginsFor, pluginsVariable, pngIcons, poolWaitMs, postgresDeviceStore, postgresHistoryStore, processor, pushMessage, queryRunnerMigration, readDbConnections, readDefinitionsVersion, readJson, readOnlyQueryTool, readOperatorSettings, readOutbox, readPlatformSignIn, readRecordTools, readVapidSettings, readWriteQueryTool, readableResources, realtimeLimits, realtimeRelayMigration, recordOf, refusalOf, refused, relayed, reloadingAdmin, 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, startWorkflow, statusMigration, statusNames, storageCleanupSchedule, storageProvider, storeFile, storeUsage, streamChatCompletion, subscription, supabasePath, thumbHash, unauthorized, updateRecordTool, urgencies, userTableResources, userTablesMigration, vapidPrivateKeyVariable, vapidPublicKeyVariable, vapidSubjectVariable, variantPath, verifySignature, verifyStandardWebhook, waitUntil, wantsLongQueries, webManifestPath, webPushSender, webauthnPath, webhooksMigration, widget, withCacheBreakpoints, withTrustedApps, workflowsMigration, writeRecordTools };
package/dist/index.js CHANGED
@@ -113,6 +113,8 @@ import { authStoreTables } from "./better-auth/auth-tables.js";
113
113
  import { checkResourcesOutsideAuthStore } from "./better-auth/store/resources-outside.js";
114
114
  import { checkComputed, computedMigration } from "./computed/computed-schema.js";
115
115
  import { checkWebhooksSchema, webhooksMigration } from "./webhooks/table/schema.js";
116
+ import { checkWorkflowsSchema, workflowsMigration } from "./workflows/schema.js";
117
+ import { startWorkflow } from "./workflows/start-workflow.js";
116
118
  import { checkUserTablesSchema, userTablesMigration } from "./user-tables/schema.js";
117
119
  import { ownedDefinitionsMigration } from "./user-tables/owned-definitions.js";
118
120
  import { userTableResources } from "./user-tables/user-table-resources.js";
@@ -140,4 +142,4 @@ import { defineEmail } from "./mail/define-email.js";
140
142
  import { definePdf } from "./pdf/define-pdf.js";
141
143
  import { startJobs } from "./jobs/runtime.js";
142
144
  import { jobsDrainApp } from "./jobs/drain-route.js";
143
- export { ConnectionsBusy, DeviceStoreError, FatalJobError, HistoryStoreError, HttpProblem, MailServerTimeoutError, MissingCredentials, ModelError, NotificationSendError, PersonalMailError, PushMessageError, addAppRoleToOwners, addCommentTool, addMember, apiKeyCaller, apiKeySettings, apiKeyVariables, appNameOver, aspectRatio, assistantProtocolRoutes, assistantRoles, assistantSettings, assistantVariables, authSchemaDrift, authSchemaMigration, authStoreTables, backgroundSettled, badRequest, batchWriteTool, beforeSignInPluginsVariable, betterAuthTokens, callsMigration, capSocketMessages, changesMigration, channelSendsMigration, checkAi, checkAuthSchema, checkCalls, checkChanges, checkChannelSends, checkComments, checkComputed, checkJobsSchema, checkNotificationsSchema, checkQueryRunner, checkRealtimeRelay, checkResourcesOutsideAuthStore, checkSourceNames, checkStatusSchema, checkUserTablesSchema, checkViewQueries, checkWebhooksSchema, coalesce, commentTools, commentsMigration, computedMigration, configExports, consoleAuditQueue, conversationKey, countRecordsTool, createAdmin, createConversations, createEncryptionTools, createFileCleanup, createOrganization, createRecordTool, createUser, defaultAppName, defaultHistoryPage, defaultSignInPolicy, defineEmail, defineFunction, defineJob, definePdf, 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, ownedDefinitionsMigration, parseColumns, parseRoles, passwordLength, pluginOptions, pluginsBeforeSignIn, pluginsFor, pluginsVariable, pngIcons, poolWaitMs, postgresDeviceStore, postgresHistoryStore, processor, pushMessage, queryRunnerMigration, readDbConnections, readDefinitionsVersion, readJson, readOnlyQueryTool, readOperatorSettings, readOutbox, readPlatformSignIn, readRecordTools, readVapidSettings, readWriteQueryTool, readableResources, realtimeLimits, realtimeRelayMigration, recordOf, refusalOf, refused, relayed, reloadingAdmin, 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, userTableResources, userTablesMigration, vapidPrivateKeyVariable, vapidPublicKeyVariable, vapidSubjectVariable, variantPath, verifySignature, verifyStandardWebhook, waitUntil, wantsLongQueries, webManifestPath, webPushSender, webauthnPath, webhooksMigration, widget, withCacheBreakpoints, withTrustedApps, writeRecordTools };
145
+ export { ConnectionsBusy, DeviceStoreError, FatalJobError, HistoryStoreError, HttpProblem, MailServerTimeoutError, MissingCredentials, ModelError, NotificationSendError, PersonalMailError, PushMessageError, addAppRoleToOwners, addCommentTool, addMember, apiKeyCaller, apiKeySettings, apiKeyVariables, appNameOver, aspectRatio, assistantProtocolRoutes, assistantRoles, assistantSettings, assistantVariables, authSchemaDrift, authSchemaMigration, authStoreTables, backgroundSettled, badRequest, batchWriteTool, beforeSignInPluginsVariable, betterAuthTokens, callsMigration, capSocketMessages, changesMigration, channelSendsMigration, checkAi, checkAuthSchema, checkCalls, checkChanges, checkChannelSends, checkComments, checkComputed, checkJobsSchema, checkNotificationsSchema, checkQueryRunner, checkRealtimeRelay, checkResourcesOutsideAuthStore, checkSourceNames, checkStatusSchema, checkUserTablesSchema, checkViewQueries, checkWebhooksSchema, checkWorkflowsSchema, coalesce, commentTools, commentsMigration, computedMigration, configExports, consoleAuditQueue, conversationKey, countRecordsTool, createAdmin, createConversations, createEncryptionTools, createFileCleanup, createOrganization, createRecordTool, createUser, defaultAppName, defaultHistoryPage, defaultSignInPolicy, defineEmail, defineFunction, defineJob, definePdf, 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, ownedDefinitionsMigration, parseColumns, parseRoles, passwordLength, pluginOptions, pluginsBeforeSignIn, pluginsFor, pluginsVariable, pngIcons, poolWaitMs, postgresDeviceStore, postgresHistoryStore, processor, pushMessage, queryRunnerMigration, readDbConnections, readDefinitionsVersion, readJson, readOnlyQueryTool, readOperatorSettings, readOutbox, readPlatformSignIn, readRecordTools, readVapidSettings, readWriteQueryTool, readableResources, realtimeLimits, realtimeRelayMigration, recordOf, refusalOf, refused, relayed, reloadingAdmin, 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, startWorkflow, statusMigration, statusNames, storageCleanupSchedule, storageProvider, storeFile, storeUsage, streamChatCompletion, subscription, supabasePath, thumbHash, unauthorized, updateRecordTool, urgencies, userTableResources, userTablesMigration, vapidPrivateKeyVariable, vapidPublicKeyVariable, vapidSubjectVariable, variantPath, verifySignature, verifyStandardWebhook, waitUntil, wantsLongQueries, webManifestPath, webPushSender, webauthnPath, webhooksMigration, widget, withCacheBreakpoints, withTrustedApps, workflowsMigration, writeRecordTools };
@@ -12,4 +12,5 @@ import { fileProviderOf } from "./files/provider-settings.js";
12
12
  import { declaredJob } from "./jobs/declared-job.js";
13
13
  import { AuthPlugins, buildAuth, createAuth, loadAuthPlugins } from "./better-auth/create-auth.js";
14
14
  import { webhooksModule } from "./webhooks/table/webhooks-module.js";
15
- export { type AppAuthInput, type AuthPlugins, appAuthOptions, appPool, betterAuthAuthenticator, buildAppAuth, buildAuth, buildAuthenticator, checkCallsOptions, checkNotificationsOptions, checkStatusOptions, createAuth, declaredJob, fileProviderOf, isSubscription, loadAuthPlugins, localFiles, longQueryPool, s3Files, webhooksModule };
15
+ import { workflowsModule } from "./workflows/workflows-module.js";
16
+ export { type AppAuthInput, type AuthPlugins, appAuthOptions, appPool, betterAuthAuthenticator, buildAppAuth, buildAuth, buildAuthenticator, checkCallsOptions, checkNotificationsOptions, checkStatusOptions, createAuth, declaredJob, fileProviderOf, isSubscription, loadAuthPlugins, localFiles, longQueryPool, s3Files, webhooksModule, workflowsModule };
package/dist/internal.js CHANGED
@@ -9,7 +9,8 @@ import { betterAuthAuthenticator } from "./better-auth/authenticator.js";
9
9
  import { fileProviderOf } from "./files/provider-settings.js";
10
10
  import { declaredJob } from "./jobs/declared-job.js";
11
11
  import { webhooksModule } from "./webhooks/table/webhooks-module.js";
12
+ import { workflowsModule } from "./workflows/workflows-module.js";
12
13
  import { appAuthOptions, buildAppAuth } from "./better-auth/auth-settings.js";
13
14
  import { buildAuthenticator } from "./better-auth/authenticate-settings.js";
14
15
  import { checkCallsOptions } from "./calls/calls-options.js";
15
- export { appAuthOptions, appPool, betterAuthAuthenticator, buildAppAuth, buildAuth, buildAuthenticator, checkCallsOptions, checkNotificationsOptions, checkStatusOptions, createAuth, declaredJob, fileProviderOf, isSubscription, loadAuthPlugins, localFiles, longQueryPool, s3Files, webhooksModule };
16
+ export { appAuthOptions, appPool, betterAuthAuthenticator, buildAppAuth, buildAuth, buildAuthenticator, checkCallsOptions, checkNotificationsOptions, checkStatusOptions, createAuth, declaredJob, fileProviderOf, isSubscription, loadAuthPlugins, localFiles, longQueryPool, s3Files, webhooksModule, workflowsModule };
@@ -19,7 +19,7 @@ const transactionStatements = (text) => {
19
19
  };
20
20
  /**
21
21
  * pg-boss's database over the app's own Kysely pool or transaction: one connection pool for the app and its jobs, the
22
- * same driver on Node, Bun and PGlite. Every statement runs on its own, or inside one transaction for pg-boss's
22
+ * same driver on Node and Bun. Every statement runs on its own, or inside one transaction for pg-boss's
23
23
  * transaction texts, which works through a pooler in transaction mode: pg-boss takes only transaction-scoped locks.
24
24
  */
25
25
  const jobsDatabase = (db) => ({ executeSql: async (text, values) => {
package/dist/package.js CHANGED
@@ -1,4 +1,4 @@
1
1
  //#region package.json
2
- var version = "0.67.7";
2
+ var version = "0.67.9";
3
3
  //#endregion
4
4
  export { version };
@@ -0,0 +1,103 @@
1
+ import { AddressRefusedError, pinnedRequest } from "../webhooks/table/pinned-request.js";
2
+ import { readLimited } from "../webhooks/table/read-limited.js";
3
+ import { isFileValue, readRunFile, storeRunFile } from "./run-files.js";
4
+ import { StepError } from "@protobase/workflows";
5
+ //#region src/workflows/http-step.ts
6
+ /** The most of an answer's body the `http` step reads; a larger one fails the step. */
7
+ const httpBodyLimit = 10485760;
8
+ /** How much of a failing answer's body its error keeps, after secrets are blanked out of it. */
9
+ const causeBodyLength = 200;
10
+ const methods = /* @__PURE__ */ new Set([
11
+ "GET",
12
+ "POST",
13
+ "PUT",
14
+ "PATCH",
15
+ "DELETE"
16
+ ]);
17
+ const readings = /* @__PURE__ */ new Set([
18
+ "json",
19
+ "text",
20
+ "file"
21
+ ]);
22
+ const invalid = (reason) => new StepError("Http.InvalidArguments", reason);
23
+ const isTextRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value) && Object.values(value).every((item) => typeof item === "string");
24
+ const checked = (args) => {
25
+ if (typeof args.url !== "string" || !URL.canParse(args.url)) throw invalid("url must be an http or https URL");
26
+ const method = args.method === void 0 ? "GET" : String(args.method).toUpperCase();
27
+ if (!methods.has(method)) throw invalid(`method must be one of ${[...methods].join(", ")}`);
28
+ if (args.headers !== void 0 && !isTextRecord(args.headers)) throw invalid("headers must be an object of text values");
29
+ if (args.query !== void 0 && !isTextRecord(args.query)) throw invalid("query must be an object of text values");
30
+ const response = args.response ?? "json";
31
+ if (typeof response !== "string" || !readings.has(response)) throw invalid("response must be json, text or file");
32
+ const url = new URL(args.url);
33
+ for (const [name, value] of Object.entries(args.query ?? {})) url.searchParams.append(name, value);
34
+ return {
35
+ url,
36
+ method,
37
+ headers: new Headers(args.headers),
38
+ response
39
+ };
40
+ };
41
+ const answerBody = async (bytes, response, url, type, keep) => {
42
+ if (response === "file") return keep(bytes, decodeURIComponent(url.pathname.slice(url.pathname.lastIndexOf("/") + 1)), type?.split(";")[0]?.trim() || "application/octet-stream");
43
+ const text = new TextDecoder().decode(bytes);
44
+ if (response === "text") return text;
45
+ if (text.trim() === "") return null;
46
+ try {
47
+ return JSON.parse(text);
48
+ } catch (error) {
49
+ if (!(error instanceof SyntaxError)) throw error;
50
+ throw new StepError("Http.InvalidBody", `${url.host} answered with a body that is not JSON; read it with "response": "text"`);
51
+ }
52
+ };
53
+ /**
54
+ * The `app:http` step: one request to `url`, built at run time, through the pinned request, which resolves its host
55
+ * once and connects only to a public address it checked. It follows no redirect, reads at most `httpBodyLimit` of
56
+ * the answer, and fails with `Http.Status` on a status outside 2xx, `Http.Network` when no answer came.
57
+ */
58
+ const httpStep = ({ allowPrivateAddresses = false, resolve, files = () => void 0 } = {}) => async (args, { runId, signal, log, redact }) => {
59
+ const given = args ?? {};
60
+ const { url, method, headers, response: reading } = checked(given);
61
+ let body;
62
+ if (given.body !== void 0 && method !== "GET") {
63
+ if (isFileValue(given.body)) {
64
+ const file = await readRunFile(files(), runId, given.body, "body");
65
+ body = file.bytes;
66
+ if (!headers.has("content-type")) headers.set("content-type", file.ref.type ?? "application/octet-stream");
67
+ } else if (typeof given.body === "string") body = given.body;
68
+ else {
69
+ body = JSON.stringify(given.body);
70
+ if (!headers.has("content-type")) headers.set("content-type", "application/json");
71
+ }
72
+ }
73
+ const started = Date.now();
74
+ const answer = await pinnedRequest(url.href, {
75
+ method,
76
+ headers,
77
+ ...body !== void 0 && { body },
78
+ signal
79
+ }, {
80
+ allowPrivateAddresses,
81
+ ...resolve && { resolve }
82
+ }).catch((error) => {
83
+ if (error instanceof AddressRefusedError) throw new StepError("Http.AddressRefused", error.message);
84
+ if (signal.aborted) throw error;
85
+ throw new StepError("Http.Network", `No answer from ${url.host}: ${error instanceof Error ? error.message : String(error)}`);
86
+ });
87
+ const bytes = await readLimited(answer, 10485761);
88
+ log(`${method} ${given.url}`.replace(/[?#].*$/, "") + `: ${answer.status} in ${Date.now() - started} ms`);
89
+ if (bytes.length > 10485760) throw new StepError("Http.ResponseTooLarge", `The answer's body is larger than ${httpBodyLimit / 1024 / 1024} MiB, the most the http step reads`);
90
+ if (answer.status < 200 || answer.status > 299) {
91
+ const excerpt = redact(new TextDecoder().decode(bytes)).slice(0, causeBodyLength);
92
+ throw new StepError("Http.Status", `${url.host} answered ${answer.status}${excerpt ? `: ${excerpt}` : ""}`);
93
+ }
94
+ const kept = Object.fromEntries([...answer.headers].filter(([name]) => name !== "set-cookie"));
95
+ const keep = (data, name, type) => storeRunFile(files(), runId, data, name, type);
96
+ return {
97
+ status: answer.status,
98
+ headers: kept,
99
+ body: await answerBody(bytes, reading, url, answer.headers.get("content-type"), keep)
100
+ };
101
+ };
102
+ //#endregion
103
+ export { httpBodyLimit, httpStep };
@@ -0,0 +1,22 @@
1
+ import { badRequest } from "../problem.js";
2
+ import { limits, nestingDepth } from "@protobase/workflows";
3
+ //#region src/workflows/input-limits.ts
4
+ /**
5
+ * Refuses a run's input over `limits.inputBytes` as JSON or nested deeper than `limits.inputDepth`, before anything
6
+ * evaluates it: a 400 problem, so the run is not created.
7
+ */
8
+ const checkRunInput = (input) => {
9
+ if (nestingDepth(input, limits.inputDepth) > limits.inputDepth) throw badRequest("input-too-deep", `A run's input nests at most ${limits.inputDepth} levels of arrays and objects`);
10
+ const bytes = new TextEncoder().encode(JSON.stringify(input ?? null)).length;
11
+ if (bytes > limits.inputBytes) throw badRequest("input-too-large", `A run's input is at most ${limits.inputBytes / 1024 / 1024} MiB as JSON; this one is ${bytes} bytes`);
12
+ };
13
+ /**
14
+ * The same limits on a run's input as the API sent it, before the body is validated, whose walk of a deeply nested
15
+ * value would overflow the call stack.
16
+ */
17
+ const runInputCheck = async ({ resource, operation, body }) => {
18
+ if (resource !== "workflow_runs" || operation !== "create" || typeof body !== "object" || body === null) return;
19
+ checkRunInput(body.input);
20
+ };
21
+ //#endregion
22
+ export { checkRunInput, runInputCheck };
@@ -0,0 +1,58 @@
1
+ import { readRunFile, runFileLimit } from "./run-files.js";
2
+ import { StepError } from "@protobase/workflows";
3
+ const invalid = (reason) => new StepError("Mail.InvalidArguments", reason);
4
+ const line = (value, name) => {
5
+ if (typeof value !== "string" || value.trim() === "") throw invalid(`${name} must be text`);
6
+ if (/[\r\n]/.test(value)) throw invalid(`${name} must be one line`);
7
+ return value;
8
+ };
9
+ const recipients = (value) => {
10
+ const list = Array.isArray(value) ? value : [value];
11
+ if (list.length === 0 || list.length > 50) throw invalid(`to must be one address or a list of 1 to 50`);
12
+ return list.map((address, index) => {
13
+ const name = Array.isArray(value) ? `to[${index}]` : "to";
14
+ if (/[,;]/.test(line(address, name))) throw invalid(`${name} must be one address; give several as a list`);
15
+ return address;
16
+ }).join(", ");
17
+ };
18
+ const attachmentsOf = async (value, files, runId) => {
19
+ if (value === void 0 || value === null) return [];
20
+ if (!Array.isArray(value)) throw invalid("attachments must be a list of file values");
21
+ const attachments = [];
22
+ let total = 0;
23
+ for (const [index, item] of value.entries()) {
24
+ const { ref, bytes } = await readRunFile(files, runId, item, `attachments[${index}]`);
25
+ total += bytes.length;
26
+ if (total > 10485760) throw invalid(`attachments are larger than ${runFileLimit / 1024 / 1024} MiB together`);
27
+ attachments.push({
28
+ filename: ref.name,
29
+ content: bytes,
30
+ ...ref.type && { contentType: ref.type }
31
+ });
32
+ }
33
+ return attachments;
34
+ };
35
+ /**
36
+ * The `app:mail.send` step: one message from the app's own sender, with the app's mailer; a workflow cannot set the
37
+ * sender. Its Message-ID comes from the step's idempotency key, the same for each try, so a receiver can tell a
38
+ * repeated try from a new mail.
39
+ */
40
+ const mailStep = (mail, files) => async (args, { runId, idempotencyKey }) => {
41
+ const given = args ?? {};
42
+ const mailer = mail();
43
+ if (!mailer) throw new StepError("States.TaskFailed", "The app sends no mail: set its mailer, or PROTOBASE_SMTP_URL and PROTOBASE_MAIL_FROM");
44
+ if (typeof given.text !== "string") throw invalid("text must be text");
45
+ if (given.html !== void 0 && typeof given.html !== "string") throw invalid("html must be text");
46
+ const messageId = `<${idempotencyKey.replace(/[^A-Za-z0-9.-]/g, ".")}@workflows.invalid>`;
47
+ await mailer.send({
48
+ to: recipients(given.to),
49
+ subject: line(given.subject, "subject"),
50
+ text: given.text,
51
+ ...given.html !== void 0 && { html: given.html },
52
+ headers: { "Message-ID": messageId },
53
+ attachments: await attachmentsOf(given.attachments, files(), runId)
54
+ });
55
+ return { message_id: messageId };
56
+ };
57
+ //#endregion
58
+ export { mailStep };
@@ -0,0 +1,27 @@
1
+ import { TenantValue } from "../types.js";
2
+ //#region src/workflows/options.d.ts
3
+ /** Workflows made in the admin app (`workflows: {}`), kept with their runs in a schema of the app's database. */
4
+ export type WorkflowsOptions = {
5
+ /** The schema of the workflows, their versions, runs and steps; default `workflows`. */
6
+ schema?: string;
7
+ /**
8
+ * The environment variables workflows may read, each as `$states.context.Secrets.<NAME>`; no other is reachable.
9
+ * Their values are blanked out of all a run records. Startup stops when one is unset or shorter than 8 characters.
10
+ */
11
+ secrets?: string[];
12
+ /**
13
+ * The roles a workflow's last editor holds now in its tenant, which its records steps act with. Default: read from
14
+ * the project's Better Auth (`auth`); a project that signs people in otherwise passes its own, which the CLI reads
15
+ * from the default export of `workflows/saved-by-roles.ts`.
16
+ */
17
+ savedByRoles?: (user: {
18
+ id: string;
19
+ tenant?: TenantValue;
20
+ }) => Promise<string[]>;
21
+ /**
22
+ * Lets the `app:http` step reach private, loopback and link-local addresses, for development and tests. Default
23
+ * false: a URL whose host resolves to one fails the step, so a workflow cannot reach the app's own network.
24
+ */
25
+ allowPrivateAddresses?: boolean;
26
+ };
27
+ //#endregion
@@ -0,0 +1,19 @@
1
+ //#region src/workflows/options.ts
2
+ const schemaPattern = /^[a-z_][a-z0-9_]*$/;
3
+ const secretName = /^[A-Z_][A-Z0-9_]*$/;
4
+ /** The schema of the options, checked. */
5
+ const workflowsSchema = (options = {}) => {
6
+ const schema = options.schema ?? "workflows";
7
+ if (!schemaPattern.test(schema)) throw new Error(`Invalid workflows schema "${schema}": use lowercase letters, digits and "_"`);
8
+ if (schema.length > 63) throw new Error(`Invalid workflows schema "${schema}": at most 63 characters`);
9
+ return schema;
10
+ };
11
+ /** The secrets workflows may read, by name, from `env`; startup stops on a name that is invalid or unset. */
12
+ const workflowSecrets = (options, env) => Object.fromEntries((options.secrets ?? []).map((name) => {
13
+ if (!secretName.test(name)) throw new Error(`workflows.secrets: "${name}" is not an environment variable name; use capitals, digits and "_"`);
14
+ const value = env[name];
15
+ if (value === void 0 || value === "") throw new Error(`workflows.secrets names ${name}, which is not set`);
16
+ return [name, value];
17
+ }));
18
+ //#endregion
19
+ export { workflowSecrets, workflowsSchema };
@@ -0,0 +1,11 @@
1
+ import { StepError } from "@protobase/workflows";
2
+ //#region src/workflows/piece-step.ts
3
+ /**
4
+ * The `app:piece` step, which a definition may name but which runs no piece yet: it fails with `Piece.Error` until the
5
+ * app's plugins give it a handler that sends through the plugin context's guarded fetch.
6
+ */
7
+ const pieceStep = async () => {
8
+ throw new StepError("Piece.Error", "No piece runs in this app yet");
9
+ };
10
+ //#endregion
11
+ export { pieceStep };
@@ -0,0 +1,114 @@
1
+ import { HttpProblem } from "../problem.js";
2
+ import { tenantScope } from "../tenant.js";
3
+ import { issueTicket } from "../files/ticket.js";
4
+ import { assistantRecords, toolEntry } from "../assistant/records.js";
5
+ import { runFileRef } from "./run-files.js";
6
+ import { sql } from "kysely";
7
+ import { StepError } from "@protobase/workflows";
8
+ //#region src/workflows/records-step.ts
9
+ const ticketSeconds = 60;
10
+ /**
11
+ * The session a run's records steps act in: the user who saved the version the run pinned, with the roles they hold
12
+ * now in the run's tenant, so a workflow never does more than its last editor may do today.
13
+ */
14
+ const saverSession = async ({ deps, schema, roles }, runId) => {
15
+ const { rows } = await sql`select v.saved_by, r.tenant_id from ${sql.id(schema, "runs")} r
16
+ join ${sql.id(schema, "versions")} v on v.workflow_id = r.workflow_id and v.version = r.version where r.id = ${runId}`.execute(deps().db);
17
+ const row = rows[0];
18
+ if (!row?.saved_by) throw new StepError("Records.Refused", "The run's version was not saved by a user, so its steps act on no records");
19
+ const tenant = row.tenant_id ?? void 0;
20
+ const held = await roles({
21
+ id: row.saved_by,
22
+ ...tenant !== void 0 && { tenant }
23
+ });
24
+ if (held.length === 0) throw new StepError("Records.Refused", "The workflow's last editor holds no role here any more, so its steps act on no records");
25
+ return {
26
+ user: {
27
+ id: row.saved_by,
28
+ roles: held
29
+ },
30
+ ...tenant !== void 0 && {
31
+ tenant,
32
+ organization: { id: tenant }
33
+ }
34
+ };
35
+ };
36
+ const withTickets = async (deps, session, resource, data, runId) => {
37
+ const entry = toolEntry(deps, resource);
38
+ const result = { ...data };
39
+ for (const [name, value] of Object.entries(data)) {
40
+ const field = entry.model.fields[name];
41
+ if (field?.type === "decimal" && typeof value === "number") result[name] = String(value);
42
+ if (!field?.file || value === null || typeof value !== "object") continue;
43
+ if (!deps.files) throw new StepError("States.TaskFailed", "The app keeps no files");
44
+ const ref = runFileRef(runId, value, `data.${name}`);
45
+ const scope = {
46
+ tenant: String(tenantScope(entry, session)?.tenantValue ?? ""),
47
+ resource: entry.name,
48
+ field: name,
49
+ user: String(session.user.id)
50
+ };
51
+ result[name] = await issueTicket(deps.files.signer, {
52
+ provider: ref.provider,
53
+ path: ref.path,
54
+ name: ref.name,
55
+ ...ref.size !== void 0 && { size: ref.size }
56
+ }, {}, scope, Math.floor(Date.now() / 1e3) + ticketSeconds);
57
+ }
58
+ return result;
59
+ };
60
+ const text = (value, name) => {
61
+ if (typeof value !== "string" || value === "") throw new StepError("Records.InvalidArguments", `${name} must be text`);
62
+ return value;
63
+ };
64
+ const keyOf = (value) => {
65
+ if (typeof value === "string" || typeof value === "number") return value;
66
+ if (Array.isArray(value) && value.every((part) => typeof part === "string" || typeof part === "number")) return value;
67
+ throw new StepError("Records.InvalidArguments", "key must be text, a number or a list of them");
68
+ };
69
+ const rowOf = (value, name) => {
70
+ if (typeof value !== "object" || value === null || Array.isArray(value)) throw new StepError("Records.InvalidArguments", `${name} must be an object of field values`);
71
+ return value;
72
+ };
73
+ const refusedAs = (error) => {
74
+ if (error instanceof HttpProblem) throw new StepError("Records.Refused", `${error.status} ${error.title}: ${error.detail}`);
75
+ throw error;
76
+ };
77
+ const run = async (input, operation, args, { runId }) => {
78
+ const resource = text(args.resource, "resource");
79
+ const deps = input.deps();
80
+ const session = await saverSession(input, runId);
81
+ const records = assistantRecords(deps, session);
82
+ if (operation === "get") return (await records.get(resource, keyOf(args.key))).record;
83
+ if (operation === "update") return (await records.update(resource, keyOf(args.key), await withTickets(deps, session, resource, rowOf(args.data, "data"), runId))).record;
84
+ if (operation === "list") return { records: (await records.list(resource, {
85
+ ...args.filter !== void 0 && { filter: text(args.filter, "filter") },
86
+ ...args.order_by !== void 0 && { order_by: text(args.order_by, "order_by") },
87
+ ...args.limit !== void 0 && { page_size: Number(args.limit) }
88
+ })).items.map(({ etag: _, permissions: __, ...record }) => record) };
89
+ if (Array.isArray(args.data)) {
90
+ const operations = [];
91
+ for (const [index, item] of args.data.entries()) operations.push({
92
+ op: "create",
93
+ resource,
94
+ data: await withTickets(deps, session, resource, rowOf(item, `data[${index}]`), runId)
95
+ });
96
+ return (await records.batch(operations)).flatMap((result) => result.op === "create" ? [result.record] : []);
97
+ }
98
+ return (await records.create(resource, await withTickets(deps, session, resource, rowOf(args.data, "data"), runId))).record;
99
+ };
100
+ /**
101
+ * The `app:records.*` steps: `create`, `update`, `get` and `list` through the app's API as the workflow's last editor,
102
+ * so its access rules, tenant scope, validation and write hooks apply as they do to that user's own requests.
103
+ */
104
+ const recordsSteps = (input) => {
105
+ const step = (operation) => (args, context) => run(input, operation, rowOf(args ?? {}, "Arguments"), context).catch(refusedAs);
106
+ return {
107
+ "app:records.create": step("create"),
108
+ "app:records.update": step("update"),
109
+ "app:records.get": step("get"),
110
+ "app:records.list": step("list")
111
+ };
112
+ };
113
+ //#endregion
114
+ export { recordsSteps };