@protobase/server 0.68.5 → 0.69.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/dist/better-auth/auth-settings.d.ts +155 -28
  2. package/dist/better-auth/auth-settings.js +3 -2
  3. package/dist/better-auth/connections/connection-account-hooks.js +62 -0
  4. package/dist/better-auth/connections/connection-events.d.ts +11 -0
  5. package/dist/better-auth/connections/connection-events.js +16 -0
  6. package/dist/better-auth/connections/connection-oauth-provider.js +129 -0
  7. package/dist/better-auth/connections/connections-plugin.js +98 -0
  8. package/dist/better-auth/connections/declared-connections.d.ts +21 -0
  9. package/dist/better-auth/connections/declared-connections.js +54 -0
  10. package/dist/better-auth/connections/revoke-token.js +34 -0
  11. package/dist/better-auth/create-auth.d.ts +162 -17
  12. package/dist/better-auth/create-auth.js +59 -5
  13. package/dist/better-auth/refresh-refusals.js +43 -0
  14. package/dist/better-auth/staff-checks.js +1 -0
  15. package/dist/create-admin.js +11 -1
  16. package/dist/forms/rate-limit.js +33 -11
  17. package/dist/forms/routes.js +1 -1
  18. package/dist/functions/verify-signature.js +4 -2
  19. package/dist/index.d.ts +2 -1
  20. package/dist/index.js +3 -2
  21. package/dist/locale/en.js +3 -0
  22. package/dist/locale/nl.js +3 -0
  23. package/dist/mcp/mcp-tools.d.ts +2 -1
  24. package/dist/mcp/mcp-tools.js +4 -2
  25. package/dist/notifications/history-routes.js +1 -1
  26. package/dist/package.js +1 -1
  27. package/dist/personal-mail/linked-accounts.js +17 -13
  28. package/dist/types.d.ts +2 -0
  29. package/dist/webhooks/attempt.js +2 -2
  30. package/dist/webhooks/table/webhook-table.js +1 -1
  31. package/dist/workflows/connections/connection-accounts.js +18 -0
  32. package/dist/workflows/connections/connection-consent.js +49 -0
  33. package/dist/workflows/connections/connection-provider.js +10 -0
  34. package/dist/workflows/connections/connection-tokens.js +54 -0
  35. package/dist/workflows/connections/connection-triggers.js +37 -0
  36. package/dist/workflows/connections/connection-wiring.js +73 -0
  37. package/dist/workflows/connections/enabler-authority.js +35 -0
  38. package/dist/workflows/connections/enabler-mailbox.js +15 -0
  39. package/dist/workflows/connections/single-flight.js +17 -0
  40. package/dist/workflows/hooks/hook-auth.js +40 -0
  41. package/dist/workflows/hooks/hook-event.js +51 -0
  42. package/dist/workflows/hooks/hook-paths.d.ts +16 -0
  43. package/dist/workflows/hooks/hook-paths.js +32 -0
  44. package/dist/workflows/hooks/hook-route.js +99 -0
  45. package/dist/workflows/hooks/hook-runs.js +32 -0
  46. package/dist/workflows/hooks/hook-secrets.js +42 -0
  47. package/dist/workflows/hooks/hook-store.js +35 -0
  48. package/dist/workflows/hooks/hooks-sql.js +20 -0
  49. package/dist/workflows/hooks/issue-body.js +21 -0
  50. package/dist/workflows/hooks/issue-route.js +23 -0
  51. package/dist/workflows/hooks/issue-webhook.js +46 -0
  52. package/dist/workflows/hooks/read-body.js +32 -0
  53. package/dist/workflows/may-update-workflow.js +26 -0
  54. package/dist/workflows/mcp/webhook-tools.d.ts +9 -0
  55. package/dist/workflows/mcp/webhook-tools.js +103 -0
  56. package/dist/workflows/mcp/workflow-instructions.js +1 -1
  57. package/dist/workflows/mcp/workflow-read-tools.d.ts +3 -0
  58. package/dist/workflows/plugin-records.js +1 -1
  59. package/dist/workflows/plugin-services.js +1 -1
  60. package/dist/workflows/ready/ready-start.js +1 -1
  61. package/dist/workflows/run-files.js +7 -1
  62. package/dist/workflows/save-hook.js +7 -2
  63. package/dist/workflows/schema-sql.js +17 -3
  64. package/dist/workflows/schema.js +3 -1
  65. package/dist/workflows/start-workflow.d.ts +3 -1
  66. package/dist/workflows/start-workflow.js +10 -4
  67. package/dist/workflows/triggers/delete-hook.js +15 -0
  68. package/dist/workflows/triggers/plugin-call.js +45 -0
  69. package/dist/workflows/triggers/poll-job.js +120 -0
  70. package/dist/workflows/triggers/poll-result.js +32 -0
  71. package/dist/workflows/triggers/poll-schedule.js +32 -0
  72. package/dist/workflows/triggers/trigger-claim.js +81 -0
  73. package/dist/workflows/triggers/trigger-jobs.js +53 -7
  74. package/dist/workflows/triggers/trigger-start.js +112 -0
  75. package/dist/workflows/triggers/trigger-sync.js +15 -10
  76. package/dist/workflows/workflow-engine.js +57 -9
  77. package/dist/workflows/workflow-types.d.ts +11 -4
  78. package/dist/workflows/workflow-types.js +21 -15
  79. package/dist/write-shared.js +1 -0
  80. package/package.json +6 -6
  81. package/dist/better-auth/end-replaced-session.d.ts +0 -17
@@ -0,0 +1,112 @@
1
+ import { callPlugin, redactAlso, triggerCall } from "./plugin-call.js";
2
+ import { stateProblem } from "./poll-result.js";
3
+ import { hookMode } from "../hooks/hook-secrets.js";
4
+ import { hookPath } from "../hooks/hook-paths.js";
5
+ import { saveHook } from "../hooks/hook-store.js";
6
+ import { backoffPollAt, firstPollAt, nextPollAt, pollEvery } from "./poll-schedule.js";
7
+ import { setNextJob } from "./trigger-claim.js";
8
+ import { renewEvery } from "./poll-job.js";
9
+ import { sql } from "kysely";
10
+ //#region src/workflows/triggers/trigger-start.ts
11
+ const stateOf = async (db, schema, { id, generation }) => (await sql`select cursor, cursor is not null as has_cursor, store is not null as has_store, consecutive_failures from ${sql.id(schema, "triggers")}
12
+ where id = ${id} and generation = ${generation} and status in ('active', 'failing')`.execute(db)).rows[0];
13
+ /**
14
+ * Records a start the plugin refused: an expired sign-in waits for a new one; any other error marks the trigger failing
15
+ * and tries the start again after the backoff.
16
+ */
17
+ const startFailed = async (db, schema, trigger, outcome, failures, restart) => {
18
+ const expired = "expired" in outcome;
19
+ const error = JSON.stringify({
20
+ code: expired ? "link_expired" : "plugin_error",
21
+ message: expired ? outcome.expired : outcome.failed,
22
+ at: (/* @__PURE__ */ new Date()).toISOString()
23
+ });
24
+ const { rows } = await sql`update ${sql.id(schema, "triggers")} set status = ${expired ? "needs_sign_in" : "failing"}, consecutive_failures = consecutive_failures + 1,
25
+ last_error = ${error}::jsonb, updated_at = now() where id = ${trigger.id} and generation = ${trigger.generation} and status in ('active', 'failing') returning 1`.execute(db);
26
+ if (rows.length > 0 && !expired) await restart(db, backoffPollAt("1 minute", failures + 1, /* @__PURE__ */ new Date()));
27
+ };
28
+ const saveStart = (db, schema, trigger, column, value, nextAt, next) => db.transaction().execute(async (trx) => {
29
+ const { rows } = await sql`update ${sql.id(schema, "triggers")} set ${sql.ref(column)} = coalesce(${sql.ref(column)}, ${JSON.stringify(value ?? null)}::jsonb), status = 'active', consecutive_failures = 0, last_error = null, next_poll_at = ${nextAt ?? null}, updated_at = now()
30
+ where id = ${trigger.id} and generation = ${trigger.generation} and status in ('active', 'failing') returning 1`.execute(trx);
31
+ if (rows.length === 0 || !next) return;
32
+ await setNextJob(trx, schema, trigger.id, await next(trx));
33
+ });
34
+ /**
35
+ * Starts a polling or subscribing webhook trigger: the plugin's `start` or `subscribe` runs only for a trigger that has
36
+ * no cursor or subscription yet, so a new version on the same input carries on where the last one was; then its first
37
+ * poll or renewal is queued, whose new job id leaves any older one with nothing to do.
38
+ */
39
+ const triggerStarts = (input, jobs) => {
40
+ const { schema, redact } = input;
41
+ const startPolling = async (db, trigger, signal, restart) => {
42
+ const type = input.polling(trigger.type);
43
+ const state = type && await stateOf(db, schema, trigger);
44
+ if (!type || !state) return;
45
+ const first = await callPlugin(async () => firstPollAt(pollEvery(type, trigger.input), /* @__PURE__ */ new Date()), redact);
46
+ if (!("value" in first)) return startFailed(db, schema, trigger, first, state.consecutive_failures, restart);
47
+ const due = first.value;
48
+ let cursor = state.cursor;
49
+ if (!state.has_cursor) {
50
+ const call = triggerCall(input, trigger, signal, redact);
51
+ const outcome = await callPlugin(() => type.start(call.context), call.redact);
52
+ if (!("value" in outcome)) return startFailed(db, schema, trigger, outcome, state.consecutive_failures, restart);
53
+ const problem = stateProblem(outcome.value);
54
+ if (problem) return startFailed(db, schema, trigger, { failed: `The start ${problem}` }, state.consecutive_failures, restart);
55
+ cursor = outcome.value;
56
+ }
57
+ await saveStart(db, schema, trigger, "cursor", cursor, due, (trx) => jobs.poll.enqueue(trx, {
58
+ trigger: trigger.id,
59
+ generation: trigger.generation,
60
+ due: due.toISOString()
61
+ }, { runAt: due }));
62
+ };
63
+ const startWebhook = async (db, trigger, signal, restart) => {
64
+ const type = input.webhooks(trigger.type);
65
+ const state = type?.subscribe && await stateOf(db, schema, trigger);
66
+ const mode = hookMode(trigger.input);
67
+ if (!type?.subscribe || !state) return;
68
+ if (state.has_store) {
69
+ if (!type.renew) return;
70
+ const now = /* @__PURE__ */ new Date();
71
+ return saveStart(db, schema, trigger, "store", null, now, (trx) => jobs.renew.enqueue(trx, {
72
+ trigger: trigger.id,
73
+ generation: trigger.generation,
74
+ due: now.toISOString()
75
+ }, { runAt: now }));
76
+ }
77
+ const origin = input.origin();
78
+ if (!origin || !mode) return startFailed(db, schema, trigger, { failed: origin ? "The trigger names an auth mode the app does not know" : "The app has no public address to subscribe with: give its auth a baseURL" }, state.consecutive_failures, restart);
79
+ const { key, secret } = await saveHook(db, schema, {
80
+ workflow: trigger.workflow_id,
81
+ tenant: trigger.tenant_id,
82
+ mode
83
+ });
84
+ const subscribe = type.subscribe;
85
+ const hidden = redactAlso([
86
+ key,
87
+ secret,
88
+ secret.replace(/^whsec_/, "")
89
+ ], redact);
90
+ const call = triggerCall(input, trigger, signal, hidden);
91
+ const outcome = await callPlugin(() => subscribe({
92
+ ...call.context,
93
+ url: `${origin}${hookPath(input.systemPath(), key)}`,
94
+ secret
95
+ }), call.redact);
96
+ if (!("value" in outcome)) return startFailed(db, schema, trigger, outcome, state.consecutive_failures, restart);
97
+ const problem = stateProblem(outcome.value);
98
+ if (problem) return startFailed(db, schema, trigger, { failed: `The subscription ${problem}` }, state.consecutive_failures, restart);
99
+ const renewal = type.renew && nextPollAt(renewEvery(type), /* @__PURE__ */ new Date());
100
+ await saveStart(db, schema, trigger, "store", outcome.value, renewal, renewal && ((trx) => jobs.renew.enqueue(trx, {
101
+ trigger: trigger.id,
102
+ generation: trigger.generation,
103
+ due: renewal.toISOString()
104
+ }, { runAt: renewal })));
105
+ };
106
+ return {
107
+ startPolling,
108
+ startWebhook
109
+ };
110
+ };
111
+ //#endregion
112
+ export { triggerStarts };
@@ -1,26 +1,31 @@
1
1
  import { badRequest } from "../../problem.js";
2
2
  import { sql } from "kysely";
3
3
  //#region src/workflows/triggers/trigger-sync.ts
4
- const triggerOf = (definition) => {
5
- const [type, input] = Object.entries(definition.trigger)[0];
6
- return type === "manual" ? void 0 : {
4
+ const triggerOf = (definition, connectionOf) => {
5
+ const document = definition;
6
+ const [type, input] = Object.entries(document.trigger)[0];
7
+ if (type === "manual") return;
8
+ const key = connectionOf[type];
9
+ return {
7
10
  type,
8
- input
11
+ input,
12
+ connection: key !== void 0 && document.connections?.[key] || null
9
13
  };
10
14
  };
11
15
  /**
12
16
  * Keeps a workflow's trigger row in step with its enabled version, in the write's transaction, queueing its jobs in that
13
17
  * same write: turning it on inserts a running trigger and queues its start; a new version with the same type and input
14
18
  * keeps the row, so its state carries over, and passes over the records again; any other change, or turning it off, stops the old row and starts a new one.
15
- * Only the current version can be turned on.
19
+ * The row keeps the account the trigger's connection binds (`connectionOf` names each type's connection), so an
20
+ * expired sign-in stops every trigger on it, and connecting it again resumes them. Only the current version can be turned on.
16
21
  */
17
- const syncTrigger = async (trx, schema, jobs, workflow) => {
22
+ const syncTrigger = async (trx, schema, jobs, workflow, connectionOf = {}) => {
18
23
  const enabled = workflow.enabled_version;
19
24
  if (enabled !== null && enabled !== workflow.version) throw badRequest("invalid-workflow", `Only the current version can be enabled: set enabled_version to ${String(workflow.version)}, or clear it to turn the workflow off`);
20
- const wanted = enabled === null ? void 0 : triggerOf(workflow.definition);
25
+ const wanted = enabled === null ? void 0 : triggerOf(workflow.definition, connectionOf);
21
26
  const triggers = sql.id(schema, "triggers");
22
27
  const id = String(workflow.id);
23
- const { rows } = await sql`select id, generation, version, (type = ${wanted?.type ?? null} and input = ${JSON.stringify(wanted?.input ?? null)}::jsonb and connection_id is null) as same
28
+ const { rows } = await sql`select id, generation, version, (type = ${wanted?.type ?? null} and input = ${JSON.stringify(wanted?.input ?? null)}::jsonb and connection_id is not distinct from ${wanted?.connection ?? null}::text) as same
24
29
  from ${triggers} where workflow_id = ${id} and status <> 'stopping'`.execute(trx);
25
30
  const existing = rows[0];
26
31
  if (existing?.same) {
@@ -37,8 +42,8 @@ const syncTrigger = async (trx, schema, jobs, workflow) => {
37
42
  await jobs.stop.enqueue(trx, { trigger: existing.id });
38
43
  }
39
44
  if (!wanted) return;
40
- const trigger = (await sql`insert into ${triggers} (workflow_id, version, tenant_id, type, input, status, generation, consecutive_failures)
41
- values (${id}, ${enabled}, ${workflow.tenant_id ?? null}, ${wanted.type}, ${JSON.stringify(wanted.input)}::jsonb, 'active', 1, 0) returning id`.execute(trx)).rows[0].id;
45
+ const trigger = (await sql`insert into ${triggers} (workflow_id, version, tenant_id, type, input, connection_id, status, generation, consecutive_failures)
46
+ values (${id}, ${enabled}, ${workflow.tenant_id ?? null}, ${wanted.type}, ${JSON.stringify(wanted.input)}::jsonb, ${wanted.connection}, 'active', 1, 0) returning id`.execute(trx)).rows[0].id;
42
47
  await jobs.start.enqueue(trx, {
43
48
  trigger,
44
49
  generation: 1
@@ -4,8 +4,15 @@ import { workflowSecrets, workflowsSchema } from "./options.js";
4
4
  import { workflowSaveHook } from "./save-hook.js";
5
5
  import { workflowsRunner } from "./segment-job.js";
6
6
  import { runStartHook } from "./start-hook.js";
7
+ import { hookRoute } from "./hooks/hook-route.js";
8
+ import { issueRoute } from "./hooks/issue-route.js";
9
+ import { workflowDeleteHook } from "./triggers/delete-hook.js";
7
10
  import { triggerJobs } from "./triggers/trigger-jobs.js";
8
11
  import { workflowTypes } from "./workflow-types.js";
12
+ import { betterAuthConnectionAccounts } from "./connections/connection-accounts.js";
13
+ import { connectionWiring } from "./connections/connection-wiring.js";
14
+ import { connectionTriggers } from "./connections/connection-triggers.js";
15
+ import { secretRedactor } from "@protobase/workflows";
9
16
  //#region src/workflows/workflow-engine.ts
10
17
  /**
11
18
  * What `createAdmin` needs for `workflows`: the hooks that check saves and start runs, the check of a run's input before
@@ -17,28 +24,69 @@ const workflowEngine = (options, deps, auth) => {
17
24
  const schema = workflowsSchema(options);
18
25
  const roles = options.savedByRoles ?? (auth && betterAuthOwnerRoles(auth));
19
26
  if (!roles) throw new Error("workflows act on records as each workflow's last editor, with their current roles: give the config `auth` (Better Auth), or `workflows.savedByRoles`");
27
+ const secrets = workflowSecrets(options, globalThis.process?.env ?? {});
28
+ const types = workflowTypes(options.plugins, (name) => deps().registry.find(name)?.model, auth?.connectionSupport);
29
+ const accounts = auth && betterAuthConnectionAccounts(auth);
30
+ const connections = connectionWiring({
31
+ deps,
32
+ schema,
33
+ roles,
34
+ accounts,
35
+ plugins: types.plugins,
36
+ connectionOf: types.connections,
37
+ ...auth && auth.personalMail.length > 0 && { personalMailer: auth.personalMailer }
38
+ });
20
39
  const input = {
21
40
  deps,
22
41
  schema,
23
42
  roles,
24
- allowPrivateAddresses: Boolean(options.allowPrivateAddresses)
43
+ allowPrivateAddresses: Boolean(options.allowPrivateAddresses),
44
+ connect: connections.trigger
25
45
  };
26
- const secrets = workflowSecrets(options, globalThis.process?.env ?? {});
27
- const types = workflowTypes(options.plugins, (name) => deps().registry.find(name)?.model);
28
46
  const { runner, job } = workflowsRunner({
29
47
  schema,
30
- tasks: (context) => types.tasks(input, context.mail),
48
+ tasks: (context) => types.tasks(input, context.mail, connections.step, connections.mailbox),
31
49
  secrets
32
50
  });
51
+ const redactor = secretRedactor(Object.values(secrets));
52
+ const usable = (found) => (type) => types.check.triggers[type] ? found.get(type) : void 0;
33
53
  const triggers = triggerJobs({
34
- deps,
35
- schema,
36
- roles,
54
+ ...input,
37
55
  declared: (type) => types.ready.get(type),
38
- readyTypes: [...types.ready.keys()].filter((id) => types.check.triggers[id] !== void 0)
56
+ readyTypes: [...types.ready.keys()].filter((id) => types.check.triggers[id] !== void 0),
57
+ polling: usable(types.polling),
58
+ webhooks: usable(types.webhooks),
59
+ redact: redactor.text,
60
+ origin: () => auth?.origins[0],
61
+ systemPath: () => deps().systemPath
39
62
  });
63
+ auth?.connections.events.set(connectionTriggers(() => deps().db, schema, triggers));
64
+ const webhooks = usable(types.webhooks);
65
+ const issue = {
66
+ deps,
67
+ schema,
68
+ webhooks,
69
+ origin: () => auth?.origins[0]
70
+ };
40
71
  return {
41
- hooks: [workflowSaveHook(schema, types.check, triggers), runStartHook(runner, deps, schema)],
72
+ routes: {
73
+ hooks: () => hookRoute({
74
+ db: deps().db,
75
+ schema,
76
+ webhooks,
77
+ webhookTypes: () => [...types.webhooks.keys()].filter((id) => webhooks(id)),
78
+ files: () => deps().files,
79
+ report: (error) => deps().report(error),
80
+ redact: redactor.text
81
+ }),
82
+ issue: () => issueRoute(issue)
83
+ },
84
+ issue,
85
+ hooks: [
86
+ workflowSaveHook(schema, types.check, triggers, connections.consent),
87
+ workflowDeleteHook(schema, triggers),
88
+ runStartHook(runner, deps, schema)
89
+ ],
42
90
  writeChecks: [runInputCheck],
43
91
  jobs: [job, ...triggers.jobs],
44
92
  onChanges: triggers.onChanges,
@@ -1,22 +1,29 @@
1
1
  import { Mailer } from "../mail/mail-message.js";
2
2
  import { ServicesInput } from "./plugin-services.js";
3
3
  import { ResourceModel } from "@protobase/schema";
4
- import { CheckOptions, LoadedPlugin, TaskHandlers } from "@protobase/workflows";
5
- import { Plugin, ReadyTrigger } from "@protobase/workflows/plugin";
4
+ import { CheckOptions, ConnectionSupport, LoadedPlugin, StepWiring, TaskHandlers } from "@protobase/workflows";
5
+ import { Plugin, PollingTrigger, ReadyTrigger, WebhookTrigger } from "@protobase/workflows/plugin";
6
6
  //#region src/workflows/workflow-types.d.ts
7
7
  type Models = (name: string) => ResourceModel | undefined;
8
8
  /**
9
9
  * The trigger and step types of an app: the built-in plugin, `app`, then the app's own, all through the same loader and
10
10
  * interface. `types` is the list the MCP tools and the editor read; `check` is what a save checks a definition against,
11
11
  * the available types only, and `checkWith` the same check against other resources, such as those a caller may read.
12
+ * A type that signs in is available once the app can make its connection, as `support` says.
12
13
  */
13
- export declare const workflowTypes: (loaded?: readonly LoadedPlugin[], models?: Models) => {
14
+ export declare const workflowTypes: (loaded?: readonly LoadedPlugin[], models?: Models, support?: ConnectionSupport) => {
14
15
  plugins: Plugin[];
15
16
  types: import("@protobase/workflows").TypeEntry[];
16
17
  check: CheckOptions;
17
18
  checkWith: (against: Models) => CheckOptions;
18
- tasks: (input: ServicesInput, mail: Mailer | undefined) => TaskHandlers;
19
+ tasks: (input: ServicesInput, mail: Mailer | undefined, connection: StepWiring["connection"], mailbox?: StepWiring["mailbox"]) => TaskHandlers;
19
20
  ready: Map<string, ReadyTrigger>;
21
+ polling: Map<string, PollingTrigger>;
22
+ webhooks: Map<string, WebhookTrigger>;
23
+ connections: {
24
+ [k: string]: string;
25
+ };
26
+ available: (id: string) => boolean;
20
27
  };
21
28
  export type WorkflowTypes = ReturnType<typeof workflowTypes>;
22
29
  //#endregion
@@ -1,30 +1,30 @@
1
1
  import { pluginServices } from "./plugin-services.js";
2
2
  import { readyProblems } from "./ready/ready-check.js";
3
- import { loadPlugins, stepHandlers, triggerChecks, typeList } from "@protobase/workflows";
4
- import { ConnectionError } from "@protobase/workflows/plugin";
3
+ import { connectionUnavailable, loadPlugins, stepHandlers, triggerChecks, typeList } from "@protobase/workflows";
5
4
  import { appPlugin } from "@protobase/workflow-plugins";
6
5
  //#region src/workflows/workflow-types.ts
7
- const unavailable = (type) => {
8
- if (type.connection) return "It signs in through a connection, which this app cannot make yet";
9
- if (type.kind === "polling" || type.kind === "webhook") return `This app does not run ${type.kind} triggers yet`;
10
- };
11
- const noConnection = (stepId, name) => ({ accessToken: () => Promise.reject(new ConnectionError("not_connected", `${stepId} signs in through "${name}", and this workflow has no sign-in for it`)) });
12
6
  const requiredInputs = (input) => Array.isArray(input.required) ? input.required : [];
7
+ /** The triggers of `plugins` of one kind, by id. */
8
+ const triggersOfKind = (plugins, kind) => new Map(plugins.flatMap((plugin) => Object.entries(plugin.triggers ?? {}).flatMap(([key, type]) => type.kind === kind ? [[`${plugin.name}:${key}`, type]] : [])));
13
9
  /** The ready triggers of `plugins`, by id. */
14
- const readyTriggers = (plugins) => new Map(plugins.flatMap((plugin) => Object.entries(plugin.triggers ?? {}).flatMap(([key, type]) => type.kind === "ready" ? [[`${plugin.name}:${key}`, type]] : [])));
10
+ const readyTriggers = (plugins) => triggersOfKind(plugins, "ready");
15
11
  /**
16
12
  * The trigger and step types of an app: the built-in plugin, `app`, then the app's own, all through the same loader and
17
13
  * interface. `types` is the list the MCP tools and the editor read; `check` is what a save checks a definition against,
18
14
  * the available types only, and `checkWith` the same check against other resources, such as those a caller may read.
15
+ * A type that signs in is available once the app can make its connection, as `support` says.
19
16
  */
20
- const workflowTypes = (loaded = [], models = () => void 0) => {
17
+ const workflowTypes = (loaded = [], models = () => void 0, support) => {
21
18
  const plugins = loadPlugins([{
22
19
  plugin: appPlugin,
23
20
  source: "@protobase/workflow-plugins"
24
21
  }, ...loaded]);
25
- const types = typeList(plugins, unavailable);
22
+ const types = typeList(plugins, connectionUnavailable(support));
23
+ const connections = Object.fromEntries(types.flatMap((type) => type.connection ? [[type.id, `${type.plugin}:${type.connection.name}`]] : []));
26
24
  const available = new Set(types.filter((type) => type.kind !== "step" && type.available).map((type) => type.id));
27
25
  const ready = readyTriggers(plugins);
26
+ const polling = triggersOfKind(plugins, "polling");
27
+ const webhooks = triggersOfKind(plugins, "webhook");
28
28
  const steps = types.filter((type) => type.kind === "step" && type.available);
29
29
  const reasons = Object.fromEntries(types.flatMap((type) => type.reason === void 0 ? [] : [[type.id, type.reason]]));
30
30
  const own = triggerChecks(plugins);
@@ -41,14 +41,16 @@ const workflowTypes = (loaded = [], models = () => void 0) => {
41
41
  resources: steps.map((type) => type.id),
42
42
  required: Object.fromEntries(steps.map((type) => [type.id, requiredInputs(type.input)])),
43
43
  triggers: Object.fromEntries(checks.filter(([id]) => available.has(id))),
44
- unavailable: reasons
44
+ unavailable: reasons,
45
+ connections
45
46
  };
46
47
  };
47
48
  const check = checkWith(models);
48
- /** The step types of one segment, which sends mail with the mailer of the job that runs it. */
49
- const tasks = (input, mail) => stepHandlers(plugins, {
49
+ /** The step types of one segment, which sends mail with the mailer of the job that runs it, signs in through `connection`, and sends from the enabler's `mailbox`. */
50
+ const tasks = (input, mail, connection, mailbox) => stepHandlers(plugins, {
50
51
  services: pluginServices(input, mail),
51
- connection: noConnection
52
+ connection,
53
+ ...mailbox && { mailbox }
52
54
  });
53
55
  return {
54
56
  plugins,
@@ -56,7 +58,11 @@ const workflowTypes = (loaded = [], models = () => void 0) => {
56
58
  check,
57
59
  checkWith,
58
60
  tasks,
59
- ready
61
+ ready,
62
+ polling,
63
+ webhooks,
64
+ connections,
65
+ available: (id) => available.has(id)
60
66
  };
61
67
  };
62
68
  //#endregion
@@ -65,6 +65,7 @@ const notifier = (deps, access, removed) => async (trx, kind, before, after) =>
65
65
  user: session.user,
66
66
  ...session.tenant !== void 0 && { tenant: session.tenant },
67
67
  ...session.anonymous && { anonymous: true },
68
+ session,
68
69
  ...before && { before: maskEncrypted(access.full, before) },
69
70
  ...after && { after: maskEncrypted(access.full, after) }
70
71
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@protobase/server",
3
- "version": "0.68.5",
3
+ "version": "0.69.0",
4
4
  "description": "The Protobase REST API as a Hono app, with Better Auth sign-in.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -44,11 +44,11 @@
44
44
  "@better-auth/sso": "^1.7.12",
45
45
  "@livekit/protocol": "1.53.1",
46
46
  "@modelcontextprotocol/server": "^2.3.1",
47
- "@protobase/layout": "0.68.5",
48
- "@protobase/query": "0.68.5",
49
- "@protobase/schema": "0.68.5",
50
- "@protobase/workflow-plugins": "0.68.5",
51
- "@protobase/workflows": "0.68.5",
47
+ "@protobase/layout": "0.69.0",
48
+ "@protobase/query": "0.69.0",
49
+ "@protobase/schema": "0.69.0",
50
+ "@protobase/workflow-plugins": "0.69.0",
51
+ "@protobase/workflows": "0.69.0",
52
52
  "@react-email/render": "^2.1.0",
53
53
  "@react-pdf/primitives": "^4.4.0",
54
54
  "@react-pdf/renderer": "^4.9.0",
@@ -1,17 +0,0 @@
1
- //#region src/better-auth/end-replaced-session.d.ts
2
- /** The part of Better Auth's endpoint context that ending a replaced session reads. */
3
- export type ReplacingContext = {
4
- getSignedCookie: (name: string, secret: string) => Promise<string | null | false>;
5
- context: {
6
- authCookies: {
7
- sessionToken: {
8
- name: string;
9
- };
10
- };
11
- secret: string;
12
- internalAdapter: {
13
- deleteSession: (token: string) => Promise<void>;
14
- };
15
- };
16
- };
17
- //#endregion