@protobase/server 0.67.12 → 0.68.1

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 (53) hide show
  1. package/dist/batch/run-batch.d.ts +1 -0
  2. package/dist/batch/run-batch.js +6 -4
  3. package/dist/check-views.js +6 -0
  4. package/dist/create-admin.js +7 -4
  5. package/dist/idempotency.d.ts +1 -0
  6. package/dist/idempotency.js +12 -0
  7. package/dist/index.d.ts +3 -2
  8. package/dist/index.js +3 -2
  9. package/dist/mcp/mcp-tools.d.ts +4 -2
  10. package/dist/mcp/mcp-tools.js +9 -3
  11. package/dist/package.js +1 -1
  12. package/dist/user-tables/admin-user-tables.js +9 -4
  13. package/dist/user-tables/definitions-views.js +93 -0
  14. package/dist/view-access.js +1 -0
  15. package/dist/webhooks/attempt.js +2 -2
  16. package/dist/webhooks/table/address-check.js +1 -1
  17. package/dist/webhooks/table/webhook-table.js +1 -1
  18. package/dist/webhooks/webhooks.js +2 -1
  19. package/dist/workflows/effects-store.js +18 -0
  20. package/dist/workflows/mcp/workflow-instructions.js +1 -1
  21. package/dist/workflows/mcp/workflow-read-tools.d.ts +11 -1
  22. package/dist/workflows/mcp/workflow-read-tools.js +14 -19
  23. package/dist/workflows/options.d.ts +6 -0
  24. package/dist/workflows/plugin-fetch.js +40 -0
  25. package/dist/workflows/plugin-records.d.ts +10 -0
  26. package/dist/workflows/plugin-records.js +120 -0
  27. package/dist/workflows/plugin-services.d.ts +13 -0
  28. package/dist/workflows/plugin-services.js +32 -0
  29. package/dist/workflows/ready/ready-check.js +62 -0
  30. package/dist/workflows/ready/ready-start.js +104 -0
  31. package/dist/workflows/ready/ready-when.js +101 -0
  32. package/dist/workflows/run-files.js +3 -7
  33. package/dist/workflows/save-hook.js +15 -13
  34. package/dist/workflows/schema-sql.js +65 -3
  35. package/dist/workflows/schema.d.ts +2 -2
  36. package/dist/workflows/schema.js +6 -2
  37. package/dist/workflows/start-workflow.d.ts +3 -1
  38. package/dist/workflows/start-workflow.js +3 -3
  39. package/dist/workflows/triggers/trigger-jobs.js +114 -0
  40. package/dist/workflows/triggers/trigger-row.js +36 -0
  41. package/dist/workflows/triggers/trigger-sync.js +48 -0
  42. package/dist/workflows/workflow-engine.js +17 -5
  43. package/dist/workflows/workflow-types-route.js +22 -0
  44. package/dist/workflows/workflow-types.d.ts +22 -0
  45. package/dist/workflows/workflow-types.js +63 -0
  46. package/dist/workflows/workflows-module.d.ts +25 -1
  47. package/package.json +6 -5
  48. package/dist/workflows/http-step.js +0 -103
  49. package/dist/workflows/mail-step.js +0 -58
  50. package/dist/workflows/mcp/type-catalog.js +0 -144
  51. package/dist/workflows/piece-step.js +0 -11
  52. package/dist/workflows/records-step.js +0 -114
  53. package/dist/workflows/step-types.js +0 -27
@@ -1,5 +1,6 @@
1
1
  import { Row } from "../types.js";
2
2
  import "../deps.js";
3
+ import "../idempotency.js";
3
4
  import { WriteResult } from "../write-shared.js";
4
5
  import "./schema.js";
5
6
  //#region src/batch/run-batch.d.ts
@@ -3,6 +3,7 @@ import { requestAccess } from "../request-access.js";
3
3
  import { writeTransaction } from "../transactions.js";
4
4
  import { actorSettings, wakeDispatch } from "../changes/actor.js";
5
5
  import { problemOf } from "../error-handler.js";
6
+ import { once } from "../idempotency.js";
6
7
  import { scheduleRemovedFiles } from "../files/attach.js";
7
8
  import { keyOf } from "../write-shared.js";
8
9
  import { checkBeforeWrite, writeIn } from "../write-pipeline.js";
@@ -27,9 +28,10 @@ const atOperation = (error, index, operation) => {
27
28
  /**
28
29
  * Runs the operations in order inside ONE transaction, each through the normal write pipeline (validation, access with row
29
30
  * filters and record-level rules, tenant, ETags, hooks), after the checks that come before it. Any failure rolls the
30
- * whole batch back and names the failing operation.
31
+ * whole batch back and names the failing operation. With `idempotency`, a batch repeated with its key writes nothing and
32
+ * returns the results of the one that committed.
31
33
  */
32
- const runBatch = async (deps, session, operations) => {
34
+ const runBatch = async (deps, session, operations, idempotency) => {
33
35
  const refs = operations.flatMap((operation) => operation.op === "create" && operation.ref ? [operation.ref] : []);
34
36
  if (new Set(refs).size !== refs.length) throw badRequest("invalid-parameter", "Each create may use a ref name once");
35
37
  const accesses = /* @__PURE__ */ new Map();
@@ -51,7 +53,7 @@ const runBatch = async (deps, session, operations) => {
51
53
  });
52
54
  }
53
55
  const removed = [];
54
- const written = await writeTransaction(deps.db, deps.statementTimeoutMs, async (trx) => {
56
+ const written = await writeTransaction(deps.db, deps.statementTimeoutMs, (trx) => once(trx, idempotency, async () => {
55
57
  const created = /* @__PURE__ */ new Map();
56
58
  const results = [];
57
59
  for (const [index, operation] of operations.entries()) try {
@@ -114,7 +116,7 @@ const runBatch = async (deps, session, operations) => {
114
116
  throw atOperation(error, index, operation);
115
117
  }
116
118
  return results;
117
- }, actorSettings(deps, session));
119
+ }), actorSettings(deps, session));
118
120
  await scheduleRemovedFiles(deps, removed);
119
121
  wakeDispatch(deps);
120
122
  return written;
@@ -69,6 +69,11 @@ const textHintErrors = (view, model) => Object.entries(view.fields).flatMap(([na
69
69
  const format = hints.format !== void 0 && textFormats.has(hints.format) ? [`format('${hints.format}') of "${name}" needs a text field, not ${field.type}`] : [];
70
70
  return [...textHints.filter((hint) => hints[hint]).map((hint) => `${hint} "${name}" needs a text field, not ${field.type}`), ...format];
71
71
  });
72
+ const notOnCreateErrors = (view, model) => Object.entries(view.fields).flatMap(([name, hints]) => {
73
+ const field = model.fields[name];
74
+ if (!hints.notOnCreate || !field || field.nullable || field.default || field.readOnly) return [];
75
+ return [`notOnCreate "${name}" is a field a create needs: make it optional or give it a default`];
76
+ });
72
77
  const linkErrors = (view, model, models, pages, createPages) => view.actions.flatMap(({ name, label, run }) => {
73
78
  if (run?.kind !== "link") return [];
74
79
  const error = linkActionError(label, run);
@@ -192,6 +197,7 @@ const checkViews = (registry, views, pages = [], fieldTypes = {}) => {
192
197
  ...valueLabelErrors(view, model),
193
198
  ...valueColorErrors(view, model),
194
199
  ...textHintErrors(view, model),
200
+ ...notOnCreateErrors(view, model),
195
201
  ...numberHintErrors(view, model),
196
202
  ...listEditErrors(view, model),
197
203
  ...widgetErrors(view, model, fieldTypes),
@@ -60,6 +60,7 @@ import { createWebhooks } from "./webhooks/webhooks.js";
60
60
  import { webhookTable } from "./webhooks/table/webhook-table.js";
61
61
  import { subscriptionUrlCheck, subscriptionWriteCheck } from "./webhooks/table/validate-hook.js";
62
62
  import { workflowEngine } from "./workflows/workflow-engine.js";
63
+ import { workflowTypesRoute } from "./workflows/workflow-types-route.js";
63
64
  import { callsService } from "./calls/calls-service.js";
64
65
  import { ringRoutes } from "./calls/ring-routes.js";
65
66
  import { upgradeAuthenticator } from "./functions/upgrade-caller.js";
@@ -119,7 +120,7 @@ import { builtInResources } from "./built-in-resources.js";
119
120
  import { adminUserTables } from "./user-tables/admin-user-tables.js";
120
121
  import { Hono } from "hono";
121
122
  import { accountPage, accountPageName, checkPages, settingsResources, withSettingsFields } from "@protobase/layout";
122
- import { guestCallsPath, ringsPath, statusPath } from "@protobase/schema/internal";
123
+ import { guestCallsPath, ringsPath, statusPath, workflowTypesPath } from "@protobase/schema/internal";
123
124
  import { getIP } from "better-auth/api";
124
125
  //#region src/create-admin.ts
125
126
  const declaredRoles = (auth, roles) => {
@@ -386,7 +387,7 @@ const createAdmin = ({ resources: appResources, views: appViews = [], userTables
386
387
  sql: options.mcp !== false && options.mcp?.sql === true,
387
388
  writes,
388
389
  commented,
389
- workflows: Boolean(engine),
390
+ ...engine && { workflows: engine.types },
390
391
  own: options.mcp !== false && options.mcp?.tools || []
391
392
  });
392
393
  const instructions = engine ? `${mcpInstructions}\n${workflowInstructions}` : mcpInstructions;
@@ -495,6 +496,7 @@ const createAdmin = ({ resources: appResources, views: appViews = [], userTables
495
496
  if (auth) removeHistoryWithUsers(auth, history);
496
497
  app.route(historyPath(systemPath), guarded((routes) => void routes.route("/", historyRoutes(history, auth?.origins ?? []))));
497
498
  }
499
+ if (engine) app.route(workflowTypesPath(systemPath), guarded((routes) => void routes.route("/", workflowTypesRoute(deps, engine.types.types))));
498
500
  if (statusTables) app.route(statusPath(systemPath), guarded((routes) => void routes.route("/", statusRoutes(db, statusTables))));
499
501
  if (deps.calls) app.route(ringsPath(systemPath), guarded((routes) => void routes.route("/", ringRoutes(deps.calls))));
500
502
  if (deps.calls && auth) {
@@ -565,7 +567,8 @@ const createAdmin = ({ resources: appResources, views: appViews = [], userTables
565
567
  channels,
566
568
  schema: changes,
567
569
  log: (line) => console.log(line),
568
- ...table && { table }
570
+ ...table && { table },
571
+ ...engine && { onChanges: engine.onChanges }
569
572
  });
570
573
  const prune = () => startRealtimePrune({
571
574
  db,
@@ -580,7 +583,7 @@ const createAdmin = ({ resources: appResources, views: appViews = [], userTables
580
583
  metaPath: `${deps.systemPath}/meta`,
581
584
  jobs: [
582
585
  ...webhooks.jobs,
583
- ...engine ? [engine.job] : [],
586
+ ...engine ? engine.jobs : [],
584
587
  ...withHistory && days !== void 0 ? [pruneHistoryJob(changes, days)] : [],
585
588
  ...mentions ? [mentions] : [],
586
589
  ...fill ? [fill] : [],
@@ -0,0 +1 @@
1
+ import { Db } from "@protobase/query";
@@ -0,0 +1,12 @@
1
+ //#region src/idempotency.ts
2
+ /** Runs `write` in `trx` once per key: a repeat returns the result the first write kept, as JSON keeps it. */
3
+ const once = async (trx, idempotency, write) => {
4
+ if (!idempotency) return write();
5
+ const first = await idempotency.store.claim(trx, idempotency.key);
6
+ if (first) return first.result;
7
+ const result = await write();
8
+ await idempotency.store.keep(trx, idempotency.key, result);
9
+ return result;
10
+ };
11
+ //#endregion
12
+ export { once };
package/dist/index.d.ts CHANGED
@@ -151,8 +151,9 @@ import { McpApprovals, mcpApprovals } from "./mcp/mcp-approval-store.js";
151
151
  import { McpServerInput, mcpServer } from "./mcp/mcp-server.js";
152
152
  import { mcpIcons } from "./web-app/mcp-icons.js";
153
153
  import { mcpInstructions } from "./mcp/mcp-instructions.js";
154
+ import { WorkflowCatalog, workflowReadTools } from "./workflows/mcp/workflow-read-tools.js";
154
155
  import { McpToolsInput, mcpTools } from "./mcp/mcp-tools.js";
155
- import { workflowReadTools } from "./workflows/mcp/workflow-read-tools.js";
156
+ import { WorkflowTypes, workflowTypes } from "./workflows/workflow-types.js";
156
157
  import { workflowWriteTools } from "./workflows/mcp/workflow-write-tools.js";
157
158
  import { workflowInstructions } from "./workflows/mcp/workflow-instructions.js";
158
159
  import { McpGateInput, mcpGate, sessionOf } from "./mcp/mcp-gate.js";
@@ -195,4 +196,4 @@ import { DrainResult, JobsRuntime, StartJobsOptions, startJobs } from "./jobs/ru
195
196
  import { jobsDrainApp } from "./jobs/drain-route.js";
196
197
  import { checkJobsSchema, jobsSchema, jobsSchemaMigration } from "./jobs/schema.js";
197
198
  import { AdminAuth, CreateAuthOptions, UserCreatedHook, normalizeRoles } from "./better-auth/create-auth.js";
198
- 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, workflowInstructions, workflowReadTools, workflowWriteTools, workflowsMigration, writeRecordTools };
199
+ 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 WorkflowCatalog, type WorkflowTypes, 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, workflowInstructions, workflowReadTools, workflowTypes, workflowWriteTools, workflowsMigration, writeRecordTools };
package/dist/index.js CHANGED
@@ -68,6 +68,8 @@ import { recordOf, resourceChange } from "./changes/resource-change.js";
68
68
  import { defineFunction } from "./functions/define-function.js";
69
69
  import { newWebhookSecret } from "./webhooks/standard-webhooks.js";
70
70
  import { subscription } from "./webhooks/subscription.js";
71
+ import { startWorkflow } from "./workflows/start-workflow.js";
72
+ import { workflowTypes } from "./workflows/workflow-types.js";
71
73
  import { historyIdPattern, historyLimits } from "./notifications/history.js";
72
74
  import { PushMessageError, maxTtlSeconds, pushMessage, urgencies } from "./notifications/message.js";
73
75
  import { NotificationSendError, maxMessageBytes, maxRecipients } from "./notifications/send-input.js";
@@ -117,7 +119,6 @@ import { checkResourcesOutsideAuthStore } from "./better-auth/store/resources-ou
117
119
  import { checkComputed, computedMigration } from "./computed/computed-schema.js";
118
120
  import { checkWebhooksSchema, webhooksMigration } from "./webhooks/table/schema.js";
119
121
  import { checkWorkflowsSchema, workflowsMigration } from "./workflows/schema.js";
120
- import { startWorkflow } from "./workflows/start-workflow.js";
121
122
  import { checkUserTablesSchema, userTablesMigration } from "./user-tables/schema.js";
122
123
  import { ownedDefinitionsMigration } from "./user-tables/owned-definitions.js";
123
124
  import { userTableResources } from "./user-tables/user-table-resources.js";
@@ -145,4 +146,4 @@ import { defineEmail } from "./mail/define-email.js";
145
146
  import { definePdf } from "./pdf/define-pdf.js";
146
147
  import { startJobs } from "./jobs/runtime.js";
147
148
  import { jobsDrainApp } from "./jobs/drain-route.js";
148
- 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, workflowInstructions, workflowReadTools, workflowWriteTools, workflowsMigration, writeRecordTools };
149
+ 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, workflowInstructions, workflowReadTools, workflowTypes, workflowWriteTools, workflowsMigration, writeRecordTools };
@@ -1,6 +1,7 @@
1
1
  import { Session } from "../types.js";
2
2
  import { AssistantTool } from "../assistant/tools.js";
3
3
  import { McpWrites } from "./mcp-writes.js";
4
+ import { WorkflowCatalog } from "../workflows/mcp/workflow-read-tools.js";
4
5
  import { Db } from "@protobase/query";
5
6
  //#region src/mcp/mcp-tools.d.ts
6
7
  export type McpToolsInput = {
@@ -20,9 +21,10 @@ export type McpToolsInput = {
20
21
  commented?: string[];
21
22
  /**
22
23
  * Offers the workflow tools, for an app with `workflows`: reading workflows, their types and runs, and checking
23
- * definitions; with writes on, `create_workflow`, `update_workflow` and `run_workflow` too. Off by default.
24
+ * definitions against the app's types; with writes on, `create_workflow`, `update_workflow` and `run_workflow` too.
25
+ * Off by default.
24
26
  */
25
- workflows?: boolean;
27
+ workflows?: WorkflowCatalog;
26
28
  /** The app's own tools, offered to every caller after the built-in ones. */
27
29
  own?: AssistantTool[];
28
30
  };
@@ -14,7 +14,13 @@ const builtInNames = (db) => new Set([
14
14
  ...writeTools({}),
15
15
  ...commentTools({ commented: ["comments"] }, {}),
16
16
  readOnlyQueryTool(db),
17
- ...workflowReadTools(),
17
+ ...workflowReadTools({
18
+ types: [],
19
+ checkWith: () => ({
20
+ resources: [],
21
+ triggers: {}
22
+ })
23
+ }),
18
24
  ...workflowWriteTools()
19
25
  ].map((tool) => tool.name));
20
26
  /** The app's own tools; one whose name is taken twice, or by a built-in tool, stops the server at startup. */
@@ -31,13 +37,13 @@ const checkedOwnTools = (db, own) => {
31
37
  * tools are built once and only chosen per session: the MCP server compiles each tool's schema, and a schema object made
32
38
  * per request would be compiled again.
33
39
  */
34
- const mcpTools = ({ db, sql, writes, commented = [], workflows = false, own = [] }) => {
40
+ const mcpTools = ({ db, sql, writes, commented = [], workflows, own = [] }) => {
35
41
  const ownTools = checkedOwnTools(db, own);
36
42
  const read = readRecordTools();
37
43
  const limits = writes === false ? false : writes === true || !writes.resources ? {} : { resources: writes.resources };
38
44
  const write = limits === false ? [] : writeTools(limits);
39
45
  const comments = commentTools({ commented }, limits);
40
- const flows = workflows ? [...workflowReadTools(), ...limits === false ? [] : workflowWriteTools(limits)] : [];
46
+ const flows = workflows ? [...workflowReadTools(workflows), ...limits === false ? [] : workflowWriteTools(limits)] : [];
41
47
  const query = sql ? [readOnlyQueryTool(db)] : [];
42
48
  return (session) => [
43
49
  ...read,
package/dist/package.js CHANGED
@@ -1,4 +1,4 @@
1
1
  //#region package.json
2
- var version = "0.67.12";
2
+ var version = "0.68.1";
3
3
  //#endregion
4
4
  export { version };
@@ -1,12 +1,13 @@
1
1
  import { definitionsResources, fieldsResourceName, tablesResourceName } from "./definitions-resources.js";
2
2
  import { definitionsHook } from "./definitions-hook.js";
3
+ import { fieldsView, tablesView } from "./definitions-views.js";
3
4
  import { servedUserTables } from "./served-user-tables.js";
4
5
  //#region src/user-tables/admin-user-tables.ts
5
6
  /**
6
7
  * What the tables users add bring to the admin, with `userTables` on: the resources over their definitions, through
7
- * which they are created, each user table a name in code (`codeNames`, the app's and Protobase's) does not take, and the
8
- * hook that creates their tables and columns. Without `tables`, nothing. With `organizations`, each definition belongs
9
- * to the organization it was made in (`definitionsResources`).
8
+ * which they are created, with the views the admin app makes them in, each user table a name in code (`codeNames`, the
9
+ * app's and Protobase's) does not take, and the hook that creates their tables and columns. Without `tables`, nothing.
10
+ * With `organizations`, each definition belongs to the organization it was made in (`definitionsResources`).
10
11
  */
11
12
  const adminUserTables = (tables, codeNames, roles, organizations = false) => {
12
13
  if (!tables) return {
@@ -31,7 +32,11 @@ const adminUserTables = (tables, codeNames, roles, organizations = false) => {
31
32
  definitions.fields,
32
33
  ...served.resources
33
34
  ],
34
- views: served.views,
35
+ views: [
36
+ tablesView,
37
+ fieldsView,
38
+ ...served.views
39
+ ],
35
40
  hooks: [definitionsHook(codeNames)],
36
41
  warnings: served.warnings
37
42
  };
@@ -0,0 +1,93 @@
1
+ import { fieldsResourceName, tablesResourceName } from "./definitions-resources.js";
2
+ import { l, view } from "@protobase/schema";
3
+ //#region src/user-tables/definitions-views.ts
4
+ /** What each field type is called in the admin app; the UI's locale files translate them. */
5
+ const fieldTypeLabels = {
6
+ text: "Text",
7
+ long_text: "Long text",
8
+ integer: "Whole number",
9
+ decimal: "Decimal number",
10
+ boolean: "Yes or no",
11
+ date: "Date",
12
+ timestamp: "Date and time",
13
+ json: "JSON"
14
+ };
15
+ /**
16
+ * The tables list of the admin app, for whoever may make tables: a table's page has its fields, a link to add one and a
17
+ * link to the table's own list, which shows once the app serves it. The title field is set once its field exists.
18
+ */
19
+ const tablesView = view(tablesResourceName).names({
20
+ singular: "Table",
21
+ plural: "Tables"
22
+ }).title((r) => r.plural_label).help("The tables people add to the app. Open one to give it fields or to go to its records.").nav({
23
+ creatorsOnly: true,
24
+ order: 0
25
+ }).fields((r) => ({
26
+ name: r.name.label("Name").format("code").help("Its address and the name of its database table: lowercase letters, digits and _, starting with a letter. It cannot change later."),
27
+ plural_label: r.plural_label.label("Plural label").help("Its name in the sidebar and above its list, such as Notes."),
28
+ singular_label: r.singular_label.label("Singular label").help("What one record is called, such as Note."),
29
+ title_field: r.title_field.label("Title field").notOnCreate().help("The name of the text field that titles each record and that search looks in.")
30
+ })).list((r) => ({
31
+ columns: [r.plural_label, r.name],
32
+ sort: [[r.name, "asc"]]
33
+ })).layout((r) => [l.section("Table", [
34
+ r.plural_label,
35
+ r.singular_label,
36
+ r.name,
37
+ r.title_field
38
+ ]), l.related("Fields", {
39
+ resource: fieldsResourceName,
40
+ field: "table_id",
41
+ columns: [
42
+ "label",
43
+ "name",
44
+ "type"
45
+ ],
46
+ sort: "position",
47
+ empty: "No fields yet."
48
+ })]).actions((a) => [a.link("open_table", {
49
+ label: "Open table",
50
+ icon: "table",
51
+ to: {
52
+ kind: "list",
53
+ resource: "{table_name}"
54
+ }
55
+ }), a.link("add_field", {
56
+ label: "Add field",
57
+ icon: "plus",
58
+ to: {
59
+ kind: "create",
60
+ resource: fieldsResourceName,
61
+ values: { table_id: "{id}" }
62
+ }
63
+ })]);
64
+ /** A table's fields, reached from its page; a field users add is always optional, so its form leaves that out. */
65
+ const fieldsView = view(fieldsResourceName).names({
66
+ singular: "Field",
67
+ plural: "Fields"
68
+ }).title((r) => r.label).nav({ hidden: true }).fields((r) => ({
69
+ table_id: r.table_id.label("Table"),
70
+ label: r.label.label("Label").help("What the field is called on screen."),
71
+ name: r.name.label("Name").format("code").help("Its name in the API and its database column: lowercase letters, digits and _, starting with a letter. It cannot change later."),
72
+ type: r.type.label("Type").valueLabels(fieldTypeLabels).help("What it holds. It cannot change later."),
73
+ position: r.position.label("Position").help("Where it sits among the fields of its table, 1 the first."),
74
+ precision: r.precision.label("Digits").help("For a decimal number only: how many digits it holds in all, from 1 to 1000."),
75
+ scale: r.scale.label("Decimals").help("For a decimal number only: how many of those digits come after the decimal point."),
76
+ required: r.required.notOnCreate()
77
+ })).list((r) => ({
78
+ columns: [
79
+ r.label,
80
+ r.name,
81
+ r.type,
82
+ r.position
83
+ ],
84
+ sort: [[r.position, "asc"]]
85
+ })).layout((r) => [l.section("Field", [
86
+ r.table_id,
87
+ r.label,
88
+ r.name,
89
+ r.type,
90
+ r.position
91
+ ]), l.section("Decimal number", [r.precision, r.scale])]);
92
+ //#endregion
93
+ export { fieldTypeLabels, fieldsView, tablesView };
@@ -306,6 +306,7 @@ const actionShape = (model, models, createPages) => shapes.object({
306
306
  const viewShape = (resource, model, models, prompted, createPages, writeOnly) => {
307
307
  const nav = shapes.object({
308
308
  hidden: shapes.data,
309
+ creatorsOnly: shapes.data,
309
310
  group: shapes.data,
310
311
  order: shapes.data,
311
312
  tenant: shapes.data,
@@ -1,9 +1,9 @@
1
- import { AddressRefusedError, pinnedRequest } from "./table/pinned-request.js";
2
- import { readLimited } from "./table/read-limited.js";
3
1
  import { invokeFunction } from "../functions/define-function.js";
4
2
  import { retryAfterMs } from "./retry-schedule.js";
5
3
  import { standardWebhookHeaders } from "./standard-webhooks.js";
6
4
  import { subscriberSession } from "./subscriber-view.js";
5
+ import { AddressRefusedError, pinnedRequest } from "./table/pinned-request.js";
6
+ import { readLimited } from "./table/read-limited.js";
7
7
  //#region src/webhooks/attempt.ts
8
8
  /** How long a receiver has to answer. */
9
9
  const attemptTimeoutMs = 15e3;
@@ -4,7 +4,7 @@ import { isIP } from "node:net";
4
4
  const v4 = (address) => address.split(".").map(Number);
5
5
  const privateV4 = (address) => {
6
6
  const [a, b] = v4(address);
7
- return a === 0 || a === 10 || a === 127 || a >= 224 || a === 100 && b >= 64 && b < 128 || a === 169 && b === 254 || a === 172 && b >= 16 && b < 32 || a === 192 && b === 168;
7
+ return a === 0 || a === 10 || a === 127 || a >= 224 || a === 100 && b >= 64 && b < 128 || a === 169 && b === 254 || a === 172 && b >= 16 && b < 32 || a === 192 && b === 168 || a === 198 && (b === 18 || b === 19);
8
8
  };
9
9
  const v6Groups = (address) => {
10
10
  const halves = address.toLowerCase().replace(/%.*$/, "").replace(/(\d+)\.(\d+)\.(\d+)\.(\d+)$/, (_, a, b, c, d) => `${(+a << 8 | +b).toString(16)}:${(+c << 8 | +d).toString(16)}`).split("::");
@@ -1,5 +1,5 @@
1
- import { checkWebhookUrl } from "./address-check.js";
2
1
  import { betterAuthOwnerRoles } from "./owner-roles.js";
2
+ import { checkWebhookUrl } from "./address-check.js";
3
3
  import { webhooksSchema } from "./options.js";
4
4
  import { tableSubscriptions } from "./table-subscriptions.js";
5
5
  //#region src/webhooks/table/webhook-table.ts
@@ -60,7 +60,7 @@ const deliveryGroup = ({ subscription, change }) => `${subscription.name}:${chan
60
60
  *
61
61
  * No jobs without a resource with events or history; the subscriptions must name declared events.
62
62
  */
63
- const createWebhooks = ({ deps, subscriptions, functions, channels, schema, log, table }) => {
63
+ const createWebhooks = ({ deps, subscriptions, functions, channels, schema, log, table, onChanges }) => {
64
64
  const entries = deps.registry.entries;
65
65
  const models = entries.map((entry) => entry.model);
66
66
  const problems = subscriptionProblems(subscriptions, models, Object.keys(functions));
@@ -292,6 +292,7 @@ const createWebhooks = ({ deps, subscriptions, functions, channels, schema, log,
292
292
  });
293
293
  await writeHistory(trx, schema, changes.filter((change) => deps.registry.find(change.resource)?.model.history));
294
294
  await writeNotices(trx, schema, changes.filter((change) => deps.registry.find(change.resource)?.model.live));
295
+ await onChanges?.(trx, changes);
295
296
  await deleteOutbox(trx, schema, rows.map((row) => row.id));
296
297
  });
297
298
  for (const [index, { subscription, event }] of now.entries()) report(subscription.name, event, 1, results[index]);
@@ -0,0 +1,18 @@
1
+ import { sql } from "kysely";
2
+ //#region src/workflows/effects-store.ts
3
+ /** The idempotency keys of one run's steps, in the `effects` table of the `workflows` schema. */
4
+ const effectsStore = (schema, runId) => {
5
+ const effects = sql.id(schema, "effects");
6
+ return {
7
+ claim: async (trx, key) => {
8
+ if ((await sql`insert into ${effects} (run_id, key) values (${runId}, ${key}) on conflict do nothing returning key`.execute(trx)).rows.length > 0) return;
9
+ const { rows } = await sql`select result from ${effects} where run_id = ${runId} and key = ${key}`.execute(trx);
10
+ return { result: rows[0].result };
11
+ },
12
+ keep: async (trx, key, result) => {
13
+ await sql`update ${effects} set result = ${JSON.stringify(result)}::jsonb where run_id = ${runId} and key = ${key}`.execute(trx);
14
+ }
15
+ };
16
+ };
17
+ //#endregion
18
+ export { effectsStore };
@@ -5,7 +5,7 @@ import { limits } from "@protobase/workflows";
5
5
  */
6
6
  const workflowInstructions = [
7
7
  "Workflows: list_workflow_types names the step types and triggers with their arguments and inputs; list_workflows and get_workflow read workflows; validate_workflow checks a definition without saving it; create_workflow and update_workflow save one; run_workflow starts a run with an input, and get_workflow_run reads it with each step's input, output, logs and error. A run goes on in the background: read it again until its status is succeeded or failed, then fix the step that failed with update_workflow and run it again.",
8
- "A definition is one JSON document: { \"trigger\": <exactly one trigger>, \"stateMachine\": { \"QueryLanguage\": \"JSONata\", \"StartAt\": \"<state>\", \"States\": { \"<state>\": <state>, ... } } }, the States Language with JSONata expressions, in a profile where every value is read by name. It holds no layout.",
8
+ "A definition is one JSON document: { \"trigger\": <trigger>, \"stateMachine\": { \"QueryLanguage\": \"JSONata\", \"StartAt\": \"<state>\", \"States\": { \"<state>\": <state>, ... } } }, the States Language with JSONata expressions, in a profile where every value is read by name. It holds no layout. <trigger> is an object with one key: { \"<trigger id>\": <its input> } for a trigger list_workflow_types names, or { \"manual\": {} } for a workflow that runs only when someone starts it.",
9
9
  "States: Task { Resource: <step type>, Arguments, TimeoutSeconds, Retry, Catch }; Pass { Output, Assign }; Choice { Choices: [{ Condition, Next, Output?, Assign? }], Default }; Map { Items, ItemProcessor: { StartAt, States }, MaxConcurrency }; Wait { Seconds | Timestamp }; Succeed { Output }; Fail { Error, Cause }. Every state may have Comment, Output and Assign. Task, Pass, Map and Wait have exactly one of Next (a state name) and End: true. State names are unique across the document, Maps included, and every state must be reachable from StartAt. InputPath, Parameters, ResultPath and the other JSONPath fields, and Parallel, are not part of the profile.",
10
10
  "Expressions: a string that is whole \"{% ... %}\" is a JSONata expression whose value, of any type, the field takes; any other string is literal text. Build text inside one expression with &: \"{% 'Order ' & $order.id %}\". Expressions sit at any depth in Arguments, Output and Assign, and in Items, Condition, Seconds, Timestamp, Error and Cause.",
11
11
  "Reading values by name, the only reads allowed: $states.context.Execution.Input (the run's input, in every state), $states.context.Execution.StartTime, $states.context.Secrets.<NAME> (a secret the app lists), $<variable> (assigned by a state that runs before), $states.result (a Task's or Map's result, in its Output and Assign), $states.errorOutput ({ Error, Cause }, in a Catch rule's Output and Assign), $states.input (the item, only in the first state of a Map's ItemProcessor). Never a bare field name, $ or $$ on its own, or $eval. Keep a result for later states with \"Assign\": { \"order\": \"{% $states.result %}\" }, read as $order from the next state on; a state inside a Map may not assign a name an outer state assigns.",
@@ -1,6 +1,16 @@
1
1
  import { AssistantTool, ToolContext } from "../../assistant/tools.js";
2
2
  import "../../index.js";
3
+ import { ResourceModel } from "@protobase/schema";
4
+ import { CheckOptions, TypeEntry } from "@protobase/workflows";
3
5
  //#region src/workflows/mcp/workflow-read-tools.d.ts
6
+ /**
7
+ * The app's types, which the tools list and check definitions against: `checkWith` checks against the resources it is
8
+ * given, so a check names only what its caller may read.
9
+ */
10
+ export type WorkflowCatalog = {
11
+ types: readonly TypeEntry[];
12
+ checkWith: (models: (name: string) => ResourceModel | undefined) => CheckOptions;
13
+ };
4
14
  /** The tools that read workflows, their types and runs, and check definitions. */
5
- export declare const workflowReadTools: () => AssistantTool<ToolContext>[];
15
+ export declare const workflowReadTools: (catalog: WorkflowCatalog) => AssistantTool<ToolContext>[];
6
16
  //#endregion
@@ -1,10 +1,8 @@
1
1
  import { defineTool } from "../../assistant/tools.js";
2
2
  import { refused } from "../../assistant/record-tool-args.js";
3
- import { stepTypeNames } from "../step-types.js";
4
3
  import { mergePatch } from "./merge-patch.js";
5
- import { stepTypeCatalog, triggerCatalog, triggerNote } from "./type-catalog.js";
6
4
  import { definitionParameter, editParameter, idOf, idParameter, objectOf, pageSizeOf, textOf } from "./workflow-tool-args.js";
7
- import { checkDefinition } from "@protobase/workflows";
5
+ import { checkDefinition, typeCatalog } from "@protobase/workflows";
8
6
  //#region src/workflows/mcp/workflow-read-tools.ts
9
7
  const reads = {
10
8
  readOnlyHint: true,
@@ -18,19 +16,15 @@ const listed = [
18
16
  "updated_at"
19
17
  ];
20
18
  /** The step types and triggers a definition may name, with their arguments, results, errors and run inputs. */
21
- const listWorkflowTypesTool = defineTool({
19
+ const listWorkflowTypesTool = (catalog) => defineTool({
22
20
  name: "list_workflow_types",
23
- description: "Lists what a workflow definition may name, as JSON ({ step_types, triggers }): each step type a Task names as its Resource, with its Arguments (required or not, and what each takes), its result and the errors it fails with; and each trigger, how the definition holds it and the input a run gets from it.",
21
+ description: "Lists what a workflow definition may name, as JSON ({ step_types, triggers }): each step type a Task names as its Resource and each trigger, by id, with its label, description, input (the Task's Arguments, or the trigger's input) and output as JSON Schema, the errors a step fails with, the connection it signs in with, and whether this app can run it.",
24
22
  parameters: {
25
23
  type: "object",
26
24
  properties: {}
27
25
  },
28
26
  annotations: reads,
29
- run: async () => JSON.stringify({
30
- step_types: stepTypeCatalog,
31
- triggers: triggerCatalog,
32
- note: triggerNote
33
- })
27
+ run: async () => JSON.stringify(typeCatalog(catalog.types))
34
28
  });
35
29
  /** The workflows the caller may read, a page at a time, without their definitions. */
36
30
  const listWorkflowsTool = defineTool({
@@ -89,15 +83,16 @@ const getWorkflowTool = defineTool({
89
83
  }), refused);
90
84
  }
91
85
  });
92
- const checked = (definition) => {
93
- const problems = checkDefinition(definition, { resources: stepTypeNames });
86
+ const checked = async (definition, catalog, context) => {
87
+ const { models } = await context.resources();
88
+ const problems = checkDefinition(definition, catalog.checkWith((name) => models.find((model) => model.name === name)));
94
89
  return JSON.stringify(problems.length > 0 ? {
95
90
  valid: false,
96
91
  problems
97
92
  } : { valid: true });
98
93
  };
99
94
  /** Checks a definition, or a saved one with an edit, without saving anything. */
100
- const validateWorkflowTool = defineTool({
95
+ const validateWorkflowTool = (catalog) => defineTool({
101
96
  name: "validate_workflow",
102
97
  description: "Checks a definition as a save checks it, and saves nothing: pass a whole definition, or a workflow's id with an edit to check that edit of its saved definition. Returns JSON { valid: true }, or { valid: false, problems }, each problem { state, field, message } naming the state, the field and what was expected.",
103
98
  parameters: {
@@ -112,8 +107,8 @@ const validateWorkflowTool = defineTool({
112
107
  run: async (args, context) => {
113
108
  const id = idOf(args);
114
109
  const edit = objectOf(args.edit);
115
- if (args.definition !== void 0 && id === void 0 && edit === void 0) return checked(args.definition);
116
- if (args.definition === void 0 && id && edit) return context.records.get("workflows", id).then(({ record }) => checked(mergePatch(record.definition, edit)), refused);
110
+ if (args.definition !== void 0 && id === void 0 && edit === void 0) return checked(args.definition, catalog, context);
111
+ if (args.definition === void 0 && id && edit) return context.records.get("workflows", id).then(({ record }) => checked(mergePatch(record.definition, edit), catalog, context), refused);
117
112
  return "Pass either { \"definition\": { ... } } or { \"id\": \"<id>\", \"edit\": { ... } }.";
118
113
  }
119
114
  });
@@ -200,12 +195,12 @@ const getWorkflowRunTool = defineTool({
200
195
  }
201
196
  });
202
197
  /** The tools that read workflows, their types and runs, and check definitions. */
203
- const workflowReadTools = () => [
204
- listWorkflowTypesTool,
198
+ const workflowReadTools = (catalog) => [
199
+ listWorkflowTypesTool(catalog),
205
200
  listWorkflowsTool,
206
201
  getWorkflowTool,
207
- validateWorkflowTool,
202
+ validateWorkflowTool(catalog),
208
203
  getWorkflowRunTool
209
204
  ];
210
205
  //#endregion
211
- export { getWorkflowRunTool, getWorkflowTool, listWorkflowTypesTool, listWorkflowsTool, validateWorkflowTool, workflowReadTools };
206
+ export { getWorkflowRunTool, getWorkflowTool, listWorkflowsTool, workflowReadTools };