@forumone/throughline-integrations 0.7.0 → 0.8.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,39 @@
1
1
  # @forumone/throughline-integrations
2
2
 
3
+ ## 0.8.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 3ca0453: Sync now: a button on the integrations document, and the endpoint behind it
8
+
9
+ Manual sync was complete except for any way to reach it. The
10
+ `integration/manual-sync` event existed, every integration handled it, the run
11
+ wrote `lastSyncAt` / `lastSyncStatus` / `lastError` and an audit row — and the
12
+ only ways in were a `trigger_sync` MCP call, which needs a minted API key and a
13
+ JSON-RPC round trip, or hand-sending the event in the Inngest dashboard. Neither
14
+ is available to the operator who has just rotated a token or fixed a record
15
+ upstream and is looking at the instance in the admin, where the next scheduled
16
+ run may be an hour away.
17
+ - **`requestManualSync()`** is now the one definition of what a trigger means:
18
+ the instance exists, the instance is enabled, this event shape. The MCP tool
19
+ calls it, and so does the new endpoint, so the two cannot drift.
20
+ - **`POST /api/<integrations>/:id/sync`** — session-cookie auth, admin only,
21
+ `202` when queued, `404` / `409` / `502` for an unknown id, a disabled
22
+ instance, and an Inngest that would not take the event.
23
+ - **A Sync now button** in the document sidebar, above `lastSyncAt`. It says the
24
+ run was queued rather than implying a result it does not have, then watches
25
+ `lastSyncAt` for two minutes and reports the outcome when it moves.
26
+
27
+ An unreachable Inngest used to escape `trigger_sync` as an unhandled rejection
28
+ and reach the caller as a generic tool failure. It is now a refusal that says
29
+ nothing was queued — the distinction a button depends on, since a sync that was
30
+ never requested otherwise looks exactly like one that has not finished.
31
+
32
+ This is the package's first admin component, so hosts must run
33
+ `payload generate:importmap` after upgrading; a stale import map 500s the admin
34
+ screen. `react` and `@payloadcms/ui` are optional peers, needed only by
35
+ `@forumone/throughline-integrations/client`.
36
+
3
37
  ## 0.7.0
4
38
 
5
39
  ### Minor Changes
package/README.md CHANGED
@@ -7,6 +7,7 @@ Plugin architecture for connecting Throughline-powered Payload sites to external
7
7
  - **`Integration` contract** — id, name, description, configFields, validateConfig, subscribes, createFunctions, mcpTools (optional), healthcheck. Every Salesforce / Mailchimp / Slack / etc. integration uses this exact shape.
8
8
  - **`IntegrationRegistry`** — process-local, per-plugin-init store keyed by integration id. Rejects duplicates synchronously.
9
9
  - **Integrations collection** — `name`, `integrationType`, `enabled`, `config` (json), and read-only `lastSyncAt` / `lastSyncStatus` / `lastError`. Admin-only writes; admin/editor reads.
10
+ - **A Sync now button**, in the document's sidebar beside those status fields, and the `POST /api/<slug>/:id/sync` endpoint behind it. See below.
10
11
  - **Five MCP tools**, handed to the host's collector at `onInit` and served by `@payloadcms/plugin-mcp` on one `/api/mcp`. Pass `mcpTools` or they reach nobody:
11
12
 
12
13
  | Tool | Use it for | Access |
@@ -19,6 +20,32 @@ Plugin architecture for connecting Throughline-powered Payload sites to external
19
20
 
20
21
  - **Webhook integration** — generic outbound HTTPS POST with HMAC-SHA256 signing, configurable event filter, retries (5x), timeout, and a HEAD-based healthcheck. RFC 4231 known-answer test vectors pin the wire format so refactoring can never silently break receivers.
21
22
 
23
+ ## Triggering a sync from the admin
24
+
25
+ Everything behind a manual sync — the `integration/manual-sync` event, a handler on every integration, the status fields, the audit rows — was reachable only over MCP or by hand-sending an event in the Inngest dashboard. Neither is available to the person who has just fixed a record in the upstream system and wants it on the site before the next cron.
26
+
27
+ So the collection ships one admin component: **Sync now**, in the sidebar, above `lastSyncAt`. It POSTs to a collection endpoint:
28
+
29
+ ```
30
+ POST /api/integrations/:id/sync { "reason": "optional" }
31
+ ```
32
+
33
+ authenticated by the Payload session cookie — no API key in the operator's path — and admin-only, matching `trigger_sync`. The endpoint and the tool both call `requestManualSync()`, which is the single definition of what a trigger checks and what event it sends; neither re-implements the rules.
34
+
35
+ | Answer | Means |
36
+ |---|---|
37
+ | `202` | Queued. The run has **not** happened yet. |
38
+ | `403` | Not an admin. |
39
+ | `404` | No instance with that id. |
40
+ | `409` | The instance is disabled. Enable it first. |
41
+ | `502` | Inngest would not take the event — nothing was queued. |
42
+
43
+ `202`, not `200`: it fires an event and returns. The button says the run was queued, then watches `lastSyncAt` for two minutes and reports the outcome when it moves — against the value the endpoint returned rather than what the page last rendered, so a cron run that lands mid-wait is not mistaken for this one. Giving up watching is not failing, and the copy says so.
44
+
45
+ `requestManualSync` and `createSyncEndpoint` are exported, so a host with its own screen can reuse either.
46
+
47
+ Because this is the package's first admin component, hosts must run `payload generate:importmap` after upgrading. A stale import map 500s the admin screen.
48
+
22
49
  ## Why this is separate from the other server packages
23
50
 
24
51
  The other server packages do one job well. This is a **framework within the framework**: a contract for integration modules plus tooling to register, configure, observe, and trigger them. Every real client engagement needs integrations; building them ad-hoc produces unmaintainable tangle. This package keeps the surface area bounded as the integration count grows.
@@ -29,7 +56,7 @@ The other server packages do one job well. This is a **framework within the fram
29
56
  pnpm add @forumone/throughline-integrations
30
57
  ```
31
58
 
32
- Peers: `payload@^3.0.0`, `inngest@^4.0.0`. Required runtime peer: `@forumone/throughline-core` (audit log).
59
+ Peers: `payload@^3.0.0`, `inngest@^4.0.0`. Required runtime peer: `@forumone/throughline-core` (audit log). `react` and `@payloadcms/ui` are optional peers, needed only to render the Sync now button — a host that never loads `@forumone/throughline-integrations/client` needs neither.
33
60
 
34
61
  ## Usage
35
62
 
@@ -71,7 +98,7 @@ Integration `createFunctions` returns Inngest functions, but **this plugin does
71
98
 
72
99
  A prompt-injection attacker could otherwise convince Claude to retarget a webhook to attacker-controlled infrastructure or rotate the signing secret. Claude can _trigger_ and _observe_ integrations conversationally, but configuration changes are deliberate human actions in the Payload admin.
73
100
 
74
- This asymmetry is intentional and is why `trigger_sync` is admin-only too — manual triggering writes to an external system, even if it doesn't change configuration.
101
+ This asymmetry is intentional and is why `trigger_sync` is admin-only too — manual triggering writes to an external system, even if it doesn't change configuration. The Sync now button and its endpoint apply the same rule, and the button does not render for a non-admin.
75
102
 
76
103
  ## Webhook details
77
104
 
@@ -0,0 +1,27 @@
1
+ import React from 'react';
2
+ export interface ThroughlineSyncButtonProps {
3
+ /** Slug the integrations collection is mounted at. Injected via `clientProps`. */
4
+ collectionSlug?: string;
5
+ }
6
+ /**
7
+ * "Sync now", in the sidebar beside the status fields it moves.
8
+ *
9
+ * Everything behind this button already existed — the `integration/manual-sync`
10
+ * event, a handler on every integration, the status fields, the audit rows —
11
+ * and reaching it meant an MCP round trip with a minted API key or a hand-sent
12
+ * event in the Inngest dashboard. Neither is available to the person who has
13
+ * just fixed a job posting and wants to see it on the site before the next
14
+ * hourly cron.
15
+ *
16
+ * It fires an event; it does not wait for the sync. So the button says the run
17
+ * was *queued*, then watches `lastSyncAt` for the two minutes a sync normally
18
+ * takes and reports the outcome when it moves. Stopping watching is not the
19
+ * same as failing, and the copy says so — the run continues either way.
20
+ *
21
+ * The form is deliberately untouched. Writing the new status into form state
22
+ * would put server values into a document the editor may not have saved, so
23
+ * the result is rendered here instead and the sidebar fields catch up on the
24
+ * next load.
25
+ */
26
+ export declare function SyncButton(props?: ThroughlineSyncButtonProps): React.ReactNode;
27
+ //# sourceMappingURL=SyncButton.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SyncButton.d.ts","sourceRoot":"","sources":["../../src/admin/SyncButton.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAmD,MAAM,OAAO,CAAA;AAWvE,MAAM,WAAW,0BAA0B;IACzC,kFAAkF;IAClF,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB;AAQD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,UAAU,CAAC,KAAK,GAAE,0BAA+B,GAAG,KAAK,CAAC,SAAS,CAyHlF"}
@@ -0,0 +1,134 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useCallback, useEffect, useRef, useState } from 'react';
4
+ import { Button, FieldLabel, toast, useAuth, useConfig, useDocumentInfo } from '@payloadcms/ui';
5
+ import { describeSyncOutcome, fetchSyncStatus, formatSyncTime, syncHasFinished, triggerSync, } from './sync-client.js';
6
+ /** How long to watch for the run to finish before saying so and stopping. */
7
+ const POLL_INTERVAL_MS = 3_000;
8
+ const POLL_TIMEOUT_MS = 120_000;
9
+ /**
10
+ * "Sync now", in the sidebar beside the status fields it moves.
11
+ *
12
+ * Everything behind this button already existed — the `integration/manual-sync`
13
+ * event, a handler on every integration, the status fields, the audit rows —
14
+ * and reaching it meant an MCP round trip with a minted API key or a hand-sent
15
+ * event in the Inngest dashboard. Neither is available to the person who has
16
+ * just fixed a job posting and wants to see it on the site before the next
17
+ * hourly cron.
18
+ *
19
+ * It fires an event; it does not wait for the sync. So the button says the run
20
+ * was *queued*, then watches `lastSyncAt` for the two minutes a sync normally
21
+ * takes and reports the outcome when it moves. Stopping watching is not the
22
+ * same as failing, and the copy says so — the run continues either way.
23
+ *
24
+ * The form is deliberately untouched. Writing the new status into form state
25
+ * would put server values into a document the editor may not have saved, so
26
+ * the result is rendered here instead and the sidebar fields catch up on the
27
+ * next load.
28
+ */
29
+ export function SyncButton(props = {}) {
30
+ const collectionSlug = props.collectionSlug ?? 'integrations';
31
+ const { id, data } = useDocumentInfo();
32
+ const { config } = useConfig();
33
+ const { user } = useAuth();
34
+ const [phase, setPhase] = useState('idle');
35
+ const [baseline, setBaseline] = useState(null);
36
+ const [outcome, setOutcome] = useState(null);
37
+ const [gaveUpWaiting, setGaveUpWaiting] = useState(false);
38
+ const { api } = config.routes;
39
+ const serverURL = config.serverURL ?? '';
40
+ const instanceName = typeof data?.['name'] === 'string' ? data['name'] : undefined;
41
+ const enabled = data?.['enabled'] === true;
42
+ const roles = user?.['roles'];
43
+ const isAdmin = Array.isArray(roles) && roles.includes('admin');
44
+ // Read inside the poll effect, which must not restart every time the name
45
+ // changes — a restarted effect is a restarted timeout.
46
+ const instanceNameRef = useRef(instanceName);
47
+ instanceNameRef.current = instanceName;
48
+ const start = useCallback(async () => {
49
+ if (id === undefined || id === null || phase !== 'idle')
50
+ return;
51
+ setPhase('triggering');
52
+ setOutcome(null);
53
+ setGaveUpWaiting(false);
54
+ const result = await triggerSync({ serverURL, apiRoute: api, collectionSlug, id });
55
+ if (!result.ok) {
56
+ toast.error(result.message);
57
+ setPhase('idle');
58
+ return;
59
+ }
60
+ toast.success(result.body.message ?? 'Sync queued.');
61
+ setBaseline(result.body.lastSyncAt ?? null);
62
+ setPhase('waiting');
63
+ }, [api, collectionSlug, id, phase, serverURL]);
64
+ useEffect(() => {
65
+ if (phase !== 'waiting' || id === undefined || id === null)
66
+ return;
67
+ const controller = new AbortController();
68
+ let timer;
69
+ const deadline = Date.now() + POLL_TIMEOUT_MS;
70
+ const poll = async () => {
71
+ const status = await fetchSyncStatus({
72
+ serverURL,
73
+ apiRoute: api,
74
+ collectionSlug,
75
+ id,
76
+ signal: controller.signal,
77
+ });
78
+ if (controller.signal.aborted)
79
+ return;
80
+ if (status && syncHasFinished(baseline, status)) {
81
+ setOutcome(status);
82
+ setPhase('idle');
83
+ const described = describeSyncOutcome(status, instanceNameRef.current);
84
+ const options = described.description ? { description: described.description } : {};
85
+ if (described.severity === 'success')
86
+ toast.success(described.title, options);
87
+ else if (described.severity === 'warning')
88
+ toast.warning(described.title, options);
89
+ else
90
+ toast.error(described.title, { ...options, duration: 10_000 });
91
+ return;
92
+ }
93
+ if (Date.now() >= deadline) {
94
+ setGaveUpWaiting(true);
95
+ setPhase('idle');
96
+ return;
97
+ }
98
+ timer = setTimeout(() => void poll(), POLL_INTERVAL_MS);
99
+ };
100
+ timer = setTimeout(() => void poll(), POLL_INTERVAL_MS);
101
+ return () => {
102
+ controller.abort();
103
+ if (timer)
104
+ clearTimeout(timer);
105
+ };
106
+ }, [api, baseline, collectionSlug, id, phase, serverURL]);
107
+ // Create view: no instance to sync, and no id to sync it by.
108
+ if (id === undefined || id === null)
109
+ return null;
110
+ // The endpoint refuses a non-admin anyway; not rendering the control is how
111
+ // an editor finds that out without pressing it.
112
+ if (!isAdmin)
113
+ return null;
114
+ const label = phase === 'idle' ? 'Sync now' : phase === 'triggering' ? 'Queueing…' : 'Syncing…';
115
+ return (_jsxs("div", { className: "field-type throughline-integration-sync", children: [_jsx(FieldLabel, { as: "span", label: "Manual sync" }), _jsx(Button, { buttonStyle: "secondary", disabled: !enabled || phase !== 'idle', onClick: () => {
116
+ void start();
117
+ }, size: "small", type: "button", ...(enabled ? {} : { tooltip: 'Enable this integration first.' }), children: label }), _jsx("p", { "aria-live": "polite", className: "field-description", children: state({ enabled, gaveUpWaiting, outcome, phase }) })] }));
118
+ }
119
+ /** The line under the button. Pure, so the copy can be read in one place. */
120
+ function state(args) {
121
+ if (!args.enabled)
122
+ return 'Disabled integrations cannot be synced. Enable and save first.';
123
+ if (args.phase === 'triggering')
124
+ return 'Asking the queue to run this integration…';
125
+ if (args.phase === 'waiting')
126
+ return 'Queued. Watching for the run to finish.';
127
+ if (args.gaveUpWaiting) {
128
+ return 'Still running after two minutes. It has not failed — reload to see the result.';
129
+ }
130
+ if (args.outcome)
131
+ return `Finished at ${formatSyncTime(args.outcome.lastSyncAt)}.`;
132
+ return 'Runs this integration now, instead of waiting for its schedule.';
133
+ }
134
+ //# sourceMappingURL=SyncButton.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SyncButton.js","sourceRoot":"","sources":["../../src/admin/SyncButton.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Client-side helpers behind the admin's Sync now button. No JSX here, so the
3
+ * request handling and the message formatting can be unit tested without a
4
+ * form, a document or a browser.
5
+ */
6
+ export type SyncStatusValue = 'never-run' | 'success' | 'partial' | 'failed';
7
+ export interface TriggerSyncBody {
8
+ ok?: boolean;
9
+ triggered?: {
10
+ instanceId: string;
11
+ instanceName: string;
12
+ type: string;
13
+ };
14
+ /** The instance's `lastSyncAt` *before* the run. The baseline to poll against. */
15
+ lastSyncAt?: string | null;
16
+ message?: string;
17
+ error?: string;
18
+ code?: string;
19
+ }
20
+ export interface TriggerSyncArgs {
21
+ serverURL: string;
22
+ apiRoute: string;
23
+ collectionSlug: string;
24
+ id: number | string;
25
+ reason?: string;
26
+ }
27
+ export type TriggerSyncResult = {
28
+ ok: true;
29
+ body: TriggerSyncBody;
30
+ } | {
31
+ ok: false;
32
+ message: string;
33
+ };
34
+ /**
35
+ * POSTs to the collection's sync endpoint using the browser's session cookie.
36
+ *
37
+ * Every refusal — not found, disabled, Inngest unreachable — comes back as a
38
+ * non-2xx with a message, so all of them land in `ok: false` and get shown.
39
+ * A button that appears to work when nothing was queued is the failure this
40
+ * exists to avoid.
41
+ */
42
+ export declare function triggerSync(args: TriggerSyncArgs): Promise<TriggerSyncResult>;
43
+ export interface SyncStatus {
44
+ lastSyncAt: string | null;
45
+ lastSyncStatus: SyncStatusValue | null;
46
+ lastError: string | null;
47
+ }
48
+ export interface FetchSyncStatusArgs {
49
+ serverURL: string;
50
+ apiRoute: string;
51
+ collectionSlug: string;
52
+ id: number | string;
53
+ signal?: AbortSignal;
54
+ }
55
+ /**
56
+ * Reads the three status fields off the instance. Returns null on any failure,
57
+ * because this is called on a timer and a single missed poll is not something
58
+ * to tell anyone about — the caller keeps waiting.
59
+ */
60
+ export declare function fetchSyncStatus(args: FetchSyncStatusArgs): Promise<null | SyncStatus>;
61
+ /**
62
+ * True once the instance has recorded a run that started after the trigger.
63
+ *
64
+ * Compared against the `lastSyncAt` the endpoint returned rather than against
65
+ * whatever the page last rendered: the sidebar can be minutes stale, and a
66
+ * document opened during a cron run would otherwise report that run's result
67
+ * as this one's.
68
+ */
69
+ export declare function syncHasFinished(baseline: null | string, current: null | SyncStatus): boolean;
70
+ export interface SyncOutcome {
71
+ severity: 'error' | 'success' | 'warning';
72
+ title: string;
73
+ description?: string;
74
+ }
75
+ /**
76
+ * How a finished run reads. `partial` is deliberately a warning rather than a
77
+ * success: some of what was asked for did not happen, and the whole reason
78
+ * somebody pressed this button is to find out whether their change landed.
79
+ */
80
+ export declare function describeSyncOutcome(status: SyncStatus, instanceName?: string): SyncOutcome;
81
+ /** `2026-08-29T14:03:00.000Z` as something an operator reads at a glance. */
82
+ export declare function formatSyncTime(value: null | string): string;
83
+ //# sourceMappingURL=sync-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync-client.d.ts","sourceRoot":"","sources":["../../src/admin/sync-client.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,MAAM,eAAe,GAAG,WAAW,GAAG,SAAS,GAAG,SAAS,GAAG,QAAQ,CAAA;AAE5E,MAAM,WAAW,eAAe;IAC9B,EAAE,CAAC,EAAE,OAAO,CAAA;IACZ,SAAS,CAAC,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;IACtE,kFAAkF;IAClF,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAED,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,cAAc,EAAE,MAAM,CAAA;IACtB,EAAE,EAAE,MAAM,GAAG,MAAM,CAAA;IACnB,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,MAAM,iBAAiB,GAAG;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,eAAe,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAA;AAEpG;;;;;;;GAOG;AACH,wBAAsB,WAAW,CAAC,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,iBAAiB,CAAC,CA2BnF;AAED,MAAM,WAAW,UAAU;IACzB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,cAAc,EAAE,eAAe,GAAG,IAAI,CAAA;IACtC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;CACzB;AAED,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,cAAc,EAAE,MAAM,CAAA;IACtB,EAAE,EAAE,MAAM,GAAG,MAAM,CAAA;IACnB,MAAM,CAAC,EAAE,WAAW,CAAA;CACrB;AAED;;;;GAIG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,GAAG,UAAU,CAAC,CAkB3F;AAMD;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,IAAI,GAAG,MAAM,EAAE,OAAO,EAAE,IAAI,GAAG,UAAU,GAAG,OAAO,CAI5F;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,OAAO,GAAG,SAAS,GAAG,SAAS,CAAA;IACzC,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,UAAU,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG,WAAW,CAwB1F;AAED,6EAA6E;AAC7E,wBAAgB,cAAc,CAAC,KAAK,EAAE,IAAI,GAAG,MAAM,GAAG,MAAM,CAK3D"}
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Client-side helpers behind the admin's Sync now button. No JSX here, so the
3
+ * request handling and the message formatting can be unit tested without a
4
+ * form, a document or a browser.
5
+ */
6
+ /**
7
+ * POSTs to the collection's sync endpoint using the browser's session cookie.
8
+ *
9
+ * Every refusal — not found, disabled, Inngest unreachable — comes back as a
10
+ * non-2xx with a message, so all of them land in `ok: false` and get shown.
11
+ * A button that appears to work when nothing was queued is the failure this
12
+ * exists to avoid.
13
+ */
14
+ export async function triggerSync(args) {
15
+ const url = `${args.serverURL}${args.apiRoute}/${args.collectionSlug}/${args.id}/sync`;
16
+ let response;
17
+ try {
18
+ response = await fetch(url, {
19
+ method: 'POST',
20
+ credentials: 'include',
21
+ headers: { 'content-type': 'application/json' },
22
+ body: JSON.stringify(args.reason ? { reason: args.reason } : {}),
23
+ });
24
+ }
25
+ catch {
26
+ return { ok: false, message: 'Could not reach the server to queue the sync.' };
27
+ }
28
+ let body;
29
+ try {
30
+ body = (await response.json());
31
+ }
32
+ catch {
33
+ return { ok: false, message: `The server returned ${response.status}.` };
34
+ }
35
+ if (!response.ok) {
36
+ return { ok: false, message: body.error ?? `The server returned ${response.status}.` };
37
+ }
38
+ return { ok: true, body };
39
+ }
40
+ /**
41
+ * Reads the three status fields off the instance. Returns null on any failure,
42
+ * because this is called on a timer and a single missed poll is not something
43
+ * to tell anyone about — the caller keeps waiting.
44
+ */
45
+ export async function fetchSyncStatus(args) {
46
+ const url = `${args.serverURL}${args.apiRoute}/${args.collectionSlug}/${args.id}?depth=0`;
47
+ try {
48
+ const response = await fetch(url, {
49
+ credentials: 'include',
50
+ ...(args.signal ? { signal: args.signal } : {}),
51
+ });
52
+ if (!response.ok)
53
+ return null;
54
+ const doc = (await response.json());
55
+ return {
56
+ lastSyncAt: typeof doc['lastSyncAt'] === 'string' ? doc['lastSyncAt'] : null,
57
+ lastSyncStatus: isSyncStatusValue(doc['lastSyncStatus']) ? doc['lastSyncStatus'] : null,
58
+ lastError: typeof doc['lastError'] === 'string' ? doc['lastError'] : null,
59
+ };
60
+ }
61
+ catch {
62
+ return null;
63
+ }
64
+ }
65
+ function isSyncStatusValue(value) {
66
+ return value === 'never-run' || value === 'success' || value === 'partial' || value === 'failed';
67
+ }
68
+ /**
69
+ * True once the instance has recorded a run that started after the trigger.
70
+ *
71
+ * Compared against the `lastSyncAt` the endpoint returned rather than against
72
+ * whatever the page last rendered: the sidebar can be minutes stale, and a
73
+ * document opened during a cron run would otherwise report that run's result
74
+ * as this one's.
75
+ */
76
+ export function syncHasFinished(baseline, current) {
77
+ if (!current)
78
+ return false;
79
+ if (current.lastSyncAt === null)
80
+ return false;
81
+ return current.lastSyncAt !== baseline;
82
+ }
83
+ /**
84
+ * How a finished run reads. `partial` is deliberately a warning rather than a
85
+ * success: some of what was asked for did not happen, and the whole reason
86
+ * somebody pressed this button is to find out whether their change landed.
87
+ */
88
+ export function describeSyncOutcome(status, instanceName) {
89
+ const subject = instanceName ? `"${instanceName}"` : 'The integration';
90
+ const description = status.lastError ?? undefined;
91
+ switch (status.lastSyncStatus) {
92
+ case 'success':
93
+ return { severity: 'success', title: `${subject} synced.` };
94
+ case 'partial':
95
+ return {
96
+ severity: 'warning',
97
+ title: `${subject} synced, with problems.`,
98
+ ...(description ? { description } : {}),
99
+ };
100
+ case 'failed':
101
+ return {
102
+ severity: 'error',
103
+ title: `${subject} failed to sync.`,
104
+ ...(description ? { description } : {}),
105
+ };
106
+ default:
107
+ // A run finished — `lastSyncAt` moved — without leaving a status. Nothing
108
+ // to celebrate and nothing to blame; say what is known.
109
+ return { severity: 'warning', title: `${subject} ran, but reported no status.` };
110
+ }
111
+ }
112
+ /** `2026-08-29T14:03:00.000Z` as something an operator reads at a glance. */
113
+ export function formatSyncTime(value) {
114
+ if (!value)
115
+ return 'Never';
116
+ const date = new Date(value);
117
+ if (Number.isNaN(date.getTime()))
118
+ return 'Unknown';
119
+ return date.toLocaleString();
120
+ }
121
+ //# sourceMappingURL=sync-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync-client.js","sourceRoot":"","sources":["../../src/admin/sync-client.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAwBH;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,IAAqB;IACrD,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,cAAc,IAAI,IAAI,CAAC,EAAE,OAAO,CAAA;IAEtF,IAAI,QAAkB,CAAA;IACtB,IAAI,CAAC;QACH,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;YAC1B,MAAM,EAAE,MAAM;YACd,WAAW,EAAE,SAAS;YACtB,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;YAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACjE,CAAC,CAAA;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,+CAA+C,EAAE,CAAA;IAChF,CAAC;IAED,IAAI,IAAqB,CAAA;IACzB,IAAI,CAAC;QACH,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAoB,CAAA;IACnD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,uBAAuB,QAAQ,CAAC,MAAM,GAAG,EAAE,CAAA;IAC1E,CAAC;IAED,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,KAAK,IAAI,uBAAuB,QAAQ,CAAC,MAAM,GAAG,EAAE,CAAA;IACxF,CAAC;IAED,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAA;AAC3B,CAAC;AAgBD;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,IAAyB;IAC7D,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,cAAc,IAAI,IAAI,CAAC,EAAE,UAAU,CAAA;IAEzF,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;YAChC,WAAW,EAAE,SAAS;YACtB,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAChD,CAAC,CAAA;QACF,IAAI,CAAC,QAAQ,CAAC,EAAE;YAAE,OAAO,IAAI,CAAA;QAC7B,MAAM,GAAG,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAA4B,CAAA;QAC9D,OAAO;YACL,UAAU,EAAE,OAAO,GAAG,CAAC,YAAY,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,IAAI;YAC5E,cAAc,EAAE,iBAAiB,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,IAAI;YACvF,SAAS,EAAE,OAAO,GAAG,CAAC,WAAW,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,IAAI;SAC1E,CAAA;IACH,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAA;IACb,CAAC;AACH,CAAC;AAED,SAAS,iBAAiB,CAAC,KAAc;IACvC,OAAO,KAAK,KAAK,WAAW,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,QAAQ,CAAA;AAClG,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,eAAe,CAAC,QAAuB,EAAE,OAA0B;IACjF,IAAI,CAAC,OAAO;QAAE,OAAO,KAAK,CAAA;IAC1B,IAAI,OAAO,CAAC,UAAU,KAAK,IAAI;QAAE,OAAO,KAAK,CAAA;IAC7C,OAAO,OAAO,CAAC,UAAU,KAAK,QAAQ,CAAA;AACxC,CAAC;AAQD;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CAAC,MAAkB,EAAE,YAAqB;IAC3E,MAAM,OAAO,GAAG,YAAY,CAAC,CAAC,CAAC,IAAI,YAAY,GAAG,CAAC,CAAC,CAAC,iBAAiB,CAAA;IACtE,MAAM,WAAW,GAAG,MAAM,CAAC,SAAS,IAAI,SAAS,CAAA;IAEjD,QAAQ,MAAM,CAAC,cAAc,EAAE,CAAC;QAC9B,KAAK,SAAS;YACZ,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,OAAO,UAAU,EAAE,CAAA;QAC7D,KAAK,SAAS;YACZ,OAAO;gBACL,QAAQ,EAAE,SAAS;gBACnB,KAAK,EAAE,GAAG,OAAO,yBAAyB;gBAC1C,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aACxC,CAAA;QACH,KAAK,QAAQ;YACX,OAAO;gBACL,QAAQ,EAAE,OAAO;gBACjB,KAAK,EAAE,GAAG,OAAO,kBAAkB;gBACnC,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aACxC,CAAA;QACH;YACE,0EAA0E;YAC1E,wDAAwD;YACxD,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,OAAO,+BAA+B,EAAE,CAAA;IACpF,CAAC;AACH,CAAC;AAED,6EAA6E;AAC7E,MAAM,UAAU,cAAc,CAAC,KAAoB;IACjD,IAAI,CAAC,KAAK;QAAE,OAAO,OAAO,CAAA;IAC1B,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,CAAA;IAC5B,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;QAAE,OAAO,SAAS,CAAA;IAClD,OAAO,IAAI,CAAC,cAAc,EAAE,CAAA;AAC9B,CAAC"}
@@ -1,8 +1,10 @@
1
- import type { CollectionConfig } from 'payload';
1
+ import type { CollectionConfig, Endpoint } from 'payload';
2
2
  import type { IntegrationRegistry } from './registry.js';
3
3
  export interface CreateIntegrationsCollectionOptions {
4
4
  slug?: string;
5
5
  registry: IntegrationRegistry;
6
+ /** Collection endpoints to mount, e.g. the manual-sync trigger. */
7
+ endpoints?: Endpoint[];
6
8
  }
7
9
  /**
8
10
  * Collection that persists per-instance integration configuration. Configuration
@@ -11,6 +13,9 @@ export interface CreateIntegrationsCollectionOptions {
11
13
  *
12
14
  * Editors can read instance status (used by the listing/status MCP tools);
13
15
  * write operations are reserved for admins editing in the Payload UI.
16
+ *
17
+ * The one control on the document that is not a field is the sidebar's Sync
18
+ * now button, which POSTs to the `:id/sync` endpoint passed in `endpoints`.
14
19
  */
15
20
  export declare function createIntegrationsCollection(options: CreateIntegrationsCollectionOptions): CollectionConfig;
16
21
  //# sourceMappingURL=collection.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"collection.d.ts","sourceRoot":"","sources":["../src/collection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAU,gBAAgB,EAAE,MAAM,SAAS,CAAA;AACvD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAA;AAGxD,MAAM,WAAW,mCAAmC;IAClD,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,mBAAmB,CAAA;CAC9B;AAYD;;;;;;;GAOG;AACH,wBAAgB,4BAA4B,CAC1C,OAAO,EAAE,mCAAmC,GAC3C,gBAAgB,CA4GlB"}
1
+ {"version":3,"file":"collection.d.ts","sourceRoot":"","sources":["../src/collection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAU,gBAAgB,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AACjE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAA;AASxD,MAAM,WAAW,mCAAmC;IAClD,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,mBAAmB,CAAA;IAC7B,mEAAmE;IACnE,SAAS,CAAC,EAAE,QAAQ,EAAE,CAAA;CACvB;AAYD;;;;;;;;;;GAUG;AACH,wBAAgB,4BAA4B,CAC1C,OAAO,EAAE,mCAAmC,GAC3C,gBAAgB,CAqIlB"}
@@ -1,4 +1,9 @@
1
1
  import { DEFAULT_INTEGRATIONS_SLUG } from './options.js';
2
+ /**
3
+ * Where Payload resolves the admin controls from. A package specifier, not a
4
+ * path, so the host's import map picks it up without any host-side file.
5
+ */
6
+ const CLIENT_ENTRY = '@forumone/throughline-integrations/client';
2
7
  const adminOrEditor = ({ req }) => {
3
8
  const roles = req.user?.['roles'] ?? [];
4
9
  return roles.includes('admin') || roles.includes('editor');
@@ -14,6 +19,9 @@ const adminOnly = ({ req }) => {
14
19
  *
15
20
  * Editors can read instance status (used by the listing/status MCP tools);
16
21
  * write operations are reserved for admins editing in the Payload UI.
22
+ *
23
+ * The one control on the document that is not a field is the sidebar's Sync
24
+ * now button, which POSTs to the `:id/sync` endpoint passed in `endpoints`.
17
25
  */
18
26
  export function createIntegrationsCollection(options) {
19
27
  const slug = options.slug ?? DEFAULT_INTEGRATIONS_SLUG;
@@ -62,6 +70,30 @@ export function createIntegrationsCollection(options) {
62
70
  description: 'Integration-specific configuration. The integration\'s configFields drive the admin UI; this JSON store carries the validated payload.',
63
71
  },
64
72
  },
73
+ {
74
+ /*
75
+ The only admin component this plugin ships. In the sidebar rather than
76
+ `beforeDocumentControls` because it belongs with the three fields it
77
+ moves — an operator watching for a sync to land is already reading
78
+ `lastSyncAt` — and because `beforeDocumentControls` also renders on the
79
+ create view, where there is no instance to sync.
80
+
81
+ A `ui` field: no column, no value, nothing to save.
82
+ */
83
+ name: 'triggerSync',
84
+ type: 'ui',
85
+ label: 'Manual sync',
86
+ admin: {
87
+ position: 'sidebar',
88
+ components: {
89
+ Field: {
90
+ path: CLIENT_ENTRY,
91
+ exportName: 'SyncButton',
92
+ clientProps: { collectionSlug: slug },
93
+ },
94
+ },
95
+ },
96
+ },
65
97
  {
66
98
  name: 'lastSyncAt',
67
99
  type: 'date',
@@ -85,6 +117,7 @@ export function createIntegrationsCollection(options) {
85
117
  admin: { readOnly: true },
86
118
  },
87
119
  ],
120
+ endpoints: options.endpoints ?? [],
88
121
  indexes: [
89
122
  { fields: ['integrationType', 'enabled'] },
90
123
  { fields: ['lastSyncStatus'] },
@@ -1 +1 @@
1
- {"version":3,"file":"collection.js","sourceRoot":"","sources":["../src/collection.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"collection.js","sourceRoot":"","sources":["../src/collection.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ import type { Endpoint } from 'payload';
2
+ import type { Inngest } from 'inngest';
3
+ export interface CreateSyncEndpointDeps {
4
+ collectionSlug: string;
5
+ inngest: Inngest;
6
+ }
7
+ /** Default recorded on the event when the caller supplies no reason of its own. */
8
+ export declare const ADMIN_TRIGGER_REASON = "Triggered from the admin.";
9
+ /**
10
+ * `POST /api/<integrations>/:id/sync` — the admin's path to the same event
11
+ * `trigger_sync` fires.
12
+ *
13
+ * Authenticates off the Payload session cookie, so the person clicking the
14
+ * button needs no API key and the event records them rather than a service
15
+ * principal. The rules are `requestManualSync`'s, not this file's; all that
16
+ * lives here is the admin check and the mapping from a refusal to a status
17
+ * code.
18
+ *
19
+ * Admin-only, matching the MCP tool and the collection's own write access:
20
+ * triggering an integration makes it POST to an external system.
21
+ */
22
+ export declare function createSyncEndpoint(deps: CreateSyncEndpointDeps): Endpoint;
23
+ //# sourceMappingURL=sync.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../src/endpoints/sync.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAkB,MAAM,SAAS,CAAA;AACvD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AAGtC,MAAM,WAAW,sBAAsB;IACrC,cAAc,EAAE,MAAM,CAAA;IACtB,OAAO,EAAE,OAAO,CAAA;CACjB;AAED,mFAAmF;AACnF,eAAO,MAAM,oBAAoB,8BAA8B,CAAA;AAY/D;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,sBAAsB,GAAG,QAAQ,CAiEzE"}
@@ -0,0 +1,85 @@
1
+ import { requestManualSync } from '../sync/manual-sync.js';
2
+ /** Default recorded on the event when the caller supplies no reason of its own. */
3
+ export const ADMIN_TRIGGER_REASON = 'Triggered from the admin.';
4
+ /** What each refusal means over HTTP. */
5
+ const STATUS = {
6
+ 'not-found': 404,
7
+ disabled: 409,
8
+ // The instance is fine; the queue is not. 502 rather than 500 says the
9
+ // failure is downstream, which is the difference between "retry" and
10
+ // "something here is broken".
11
+ 'send-failed': 502,
12
+ };
13
+ /**
14
+ * `POST /api/<integrations>/:id/sync` — the admin's path to the same event
15
+ * `trigger_sync` fires.
16
+ *
17
+ * Authenticates off the Payload session cookie, so the person clicking the
18
+ * button needs no API key and the event records them rather than a service
19
+ * principal. The rules are `requestManualSync`'s, not this file's; all that
20
+ * lives here is the admin check and the mapping from a refusal to a status
21
+ * code.
22
+ *
23
+ * Admin-only, matching the MCP tool and the collection's own write access:
24
+ * triggering an integration makes it POST to an external system.
25
+ */
26
+ export function createSyncEndpoint(deps) {
27
+ return {
28
+ path: '/:id/sync',
29
+ method: 'post',
30
+ handler: async (req) => {
31
+ if (!req.user) {
32
+ return json({ error: 'You must be logged in to trigger a sync.' }, 401);
33
+ }
34
+ const roles = req.user['roles'] ?? [];
35
+ if (!roles.includes('admin')) {
36
+ return json({ error: 'Only admins can manually trigger integrations.' }, 403);
37
+ }
38
+ const id = req.routeParams?.['id'];
39
+ if (typeof id !== 'string' && typeof id !== 'number') {
40
+ return json({ error: 'Expected an integration instance id in the path.' }, 400);
41
+ }
42
+ // The button sends no body at all, so an absent or unparseable one is
43
+ // not an error — only a body that is present and wrong.
44
+ let reason = null;
45
+ try {
46
+ const raw = (await req.json?.());
47
+ if (raw && typeof raw === 'object' && typeof raw.reason === 'string') {
48
+ reason = raw.reason.trim() || null;
49
+ }
50
+ }
51
+ catch {
52
+ reason = null;
53
+ }
54
+ const result = await requestManualSync({ payload: req.payload, collectionSlug: deps.collectionSlug, inngest: deps.inngest }, {
55
+ instanceId: String(id),
56
+ triggeredBy: req.user.id ?? null,
57
+ reason: reason ?? `${ADMIN_TRIGGER_REASON} (${req.user['email'] ?? req.user.id})`,
58
+ });
59
+ if (!result.ok) {
60
+ if (result.code === 'send-failed') {
61
+ req.payload.logger.error(`[integrations] could not queue a manual sync for ${String(id)}: ${result.message}`);
62
+ }
63
+ return json({ error: result.message, code: result.code }, STATUS[result.code]);
64
+ }
65
+ return json({
66
+ ok: true,
67
+ triggered: {
68
+ instanceId: result.instanceId,
69
+ instanceName: result.instanceName,
70
+ type: result.integrationType,
71
+ },
72
+ // What the caller polls against. The sync has not run yet.
73
+ lastSyncAt: result.lastSyncAt,
74
+ message: `Queued a sync for "${result.instanceName}". The status fields update when the run finishes.`,
75
+ }, 202);
76
+ },
77
+ };
78
+ }
79
+ function json(body, status) {
80
+ return new Response(JSON.stringify(body), {
81
+ status,
82
+ headers: { 'content-type': 'application/json' },
83
+ });
84
+ }
85
+ //# sourceMappingURL=sync.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync.js","sourceRoot":"","sources":["../../src/endpoints/sync.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,iBAAiB,EAA0B,MAAM,wBAAwB,CAAA;AAOlF,mFAAmF;AACnF,MAAM,CAAC,MAAM,oBAAoB,GAAG,2BAA2B,CAAA;AAE/D,yCAAyC;AACzC,MAAM,MAAM,GAAsC;IAChD,WAAW,EAAE,GAAG;IAChB,QAAQ,EAAE,GAAG;IACb,uEAAuE;IACvE,qEAAqE;IACrE,8BAA8B;IAC9B,aAAa,EAAE,GAAG;CACnB,CAAA;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAA4B;IAC7D,OAAO;QACL,IAAI,EAAE,WAAW;QACjB,MAAM,EAAE,MAAM;QACd,OAAO,EAAE,KAAK,EAAE,GAAmB,EAAqB,EAAE;YACxD,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;gBACd,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,0CAA0C,EAAE,EAAE,GAAG,CAAC,CAAA;YACzE,CAAC;YAED,MAAM,KAAK,GAAI,GAAG,CAAC,IAAI,CAAC,OAAO,CAA0B,IAAI,EAAE,CAAA;YAC/D,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;gBAC7B,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,gDAAgD,EAAE,EAAE,GAAG,CAAC,CAAA;YAC/E,CAAC;YAED,MAAM,EAAE,GAAG,GAAG,CAAC,WAAW,EAAE,CAAC,IAAI,CAAC,CAAA;YAClC,IAAI,OAAO,EAAE,KAAK,QAAQ,IAAI,OAAO,EAAE,KAAK,QAAQ,EAAE,CAAC;gBACrD,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,kDAAkD,EAAE,EAAE,GAAG,CAAC,CAAA;YACjF,CAAC;YAED,sEAAsE;YACtE,wDAAwD;YACxD,IAAI,MAAM,GAAkB,IAAI,CAAA;YAChC,IAAI,CAAC;gBACH,MAAM,GAAG,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,EAAE,CAAqC,CAAA;gBACpE,IAAI,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,OAAO,GAAG,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;oBACrE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,IAAI,CAAA;gBACpC,CAAC;YACH,CAAC;YAAC,MAAM,CAAC;gBACP,MAAM,GAAG,IAAI,CAAA;YACf,CAAC;YAED,MAAM,MAAM,GAAG,MAAM,iBAAiB,CACpC,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,cAAc,EAAE,IAAI,CAAC,cAAc,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,EACpF;gBACE,UAAU,EAAE,MAAM,CAAC,EAAE,CAAC;gBACtB,WAAW,EAAE,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,IAAI;gBAChC,MAAM,EAAE,MAAM,IAAI,GAAG,oBAAoB,KAAK,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,EAAE,GAAG;aAClF,CACF,CAAA;YAED,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;gBACf,IAAI,MAAM,CAAC,IAAI,KAAK,aAAa,EAAE,CAAC;oBAClC,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CACtB,oDAAoD,MAAM,CAAC,EAAE,CAAC,KAAK,MAAM,CAAC,OAAO,EAAE,CACpF,CAAA;gBACH,CAAC;gBACD,OAAO,IAAI,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAA;YAChF,CAAC;YAED,OAAO,IAAI,CACT;gBACE,EAAE,EAAE,IAAI;gBACR,SAAS,EAAE;oBACT,UAAU,EAAE,MAAM,CAAC,UAAU;oBAC7B,YAAY,EAAE,MAAM,CAAC,YAAY;oBACjC,IAAI,EAAE,MAAM,CAAC,eAAe;iBAC7B;gBACD,2DAA2D;gBAC3D,UAAU,EAAE,MAAM,CAAC,UAAU;gBAC7B,OAAO,EAAE,sBAAsB,MAAM,CAAC,YAAY,oDAAoD;aACvG,EACD,GAAG,CACJ,CAAA;QACH,CAAC;KACF,CAAA;AACH,CAAC;AAED,SAAS,IAAI,CAAC,IAAa,EAAE,MAAc;IACzC,OAAO,IAAI,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE;QACxC,MAAM;QACN,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;KAChD,CAAC,CAAA;AACJ,CAAC"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Client entry point for the admin controls, referenced from Payload's import
3
+ * map as `@forumone/throughline-integrations/client#SyncButton`.
4
+ *
5
+ * Kept separate from the package root so server-only consumers never pull React
6
+ * or `@payloadcms/ui` into their bundle.
7
+ */
8
+ export { SyncButton } from '../admin/SyncButton.js';
9
+ export type { ThroughlineSyncButtonProps } from '../admin/SyncButton.js';
10
+ export { describeSyncOutcome, fetchSyncStatus, formatSyncTime, syncHasFinished, triggerSync, } from '../admin/sync-client.js';
11
+ export type { FetchSyncStatusArgs, SyncOutcome, SyncStatus, SyncStatusValue, TriggerSyncArgs, TriggerSyncBody, TriggerSyncResult, } from '../admin/sync-client.js';
12
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/exports/client.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAA;AACnD,YAAY,EAAE,0BAA0B,EAAE,MAAM,wBAAwB,CAAA;AAExE,OAAO,EACL,mBAAmB,EACnB,eAAe,EACf,cAAc,EACd,eAAe,EACf,WAAW,GACZ,MAAM,yBAAyB,CAAA;AAChC,YAAY,EACV,mBAAmB,EACnB,WAAW,EACX,UAAU,EACV,eAAe,EACf,eAAe,EACf,eAAe,EACf,iBAAiB,GAClB,MAAM,yBAAyB,CAAA"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Client entry point for the admin controls, referenced from Payload's import
3
+ * map as `@forumone/throughline-integrations/client#SyncButton`.
4
+ *
5
+ * Kept separate from the package root so server-only consumers never pull React
6
+ * or `@payloadcms/ui` into their bundle.
7
+ */
8
+ export { SyncButton } from '../admin/SyncButton.js';
9
+ export { describeSyncOutcome, fetchSyncStatus, formatSyncTime, syncHasFinished, triggerSync, } from '../admin/sync-client.js';
10
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/exports/client.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAA;AAGnD,OAAO,EACL,mBAAmB,EACnB,eAAe,EACf,cAAc,EACd,eAAe,EACf,WAAW,GACZ,MAAM,yBAAyB,CAAA"}
package/dist/index.d.ts CHANGED
@@ -5,5 +5,9 @@ export type { IntegrationsPluginOptions } from './options.js';
5
5
  export { webhookIntegration, WEBHOOK_INTEGRATION_ID } from './integrations/index.js';
6
6
  export type { WebhookConfig } from './integrations/index.js';
7
7
  export type { Integration, IntegrationCategory, IntegrationContext, IntegrationAuditEvent, IntegrationConfigValidation, IntegrationHealth, IntegrationInstance, IntegrationInstanceLoaded, IntegrationSyncStatus, } from './types.js';
8
+ export { requestManualSync, MANUAL_SYNC_EVENT } from './sync/manual-sync.js';
9
+ export type { ManualSyncRefusal, RequestManualSyncArgs, RequestManualSyncDeps, RequestManualSyncResult, } from './sync/manual-sync.js';
10
+ export { createSyncEndpoint, ADMIN_TRIGGER_REASON } from './endpoints/sync.js';
11
+ export type { CreateSyncEndpointDeps } from './endpoints/sync.js';
8
12
  export { createListIntegrationsTool, createGetIntegrationStatusTool, createTriggerSyncTool, createTestIntegrationTool, createListIntegrationTypesTool, } from './tools/index.js';
9
13
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,EAClB,sBAAsB,EACtB,qBAAqB,GACtB,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAA;AACnD,OAAO,EAAE,yBAAyB,EAAE,MAAM,cAAc,CAAA;AACxD,YAAY,EAAE,yBAAyB,EAAE,MAAM,cAAc,CAAA;AAE7D,OAAO,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAA;AACpF,YAAY,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAA;AAE5D,YAAY,EACV,WAAW,EACX,mBAAmB,EACnB,kBAAkB,EAClB,qBAAqB,EACrB,2BAA2B,EAC3B,iBAAiB,EACjB,mBAAmB,EACnB,yBAAyB,EACzB,qBAAqB,GACtB,MAAM,YAAY,CAAA;AAEnB,OAAO,EACL,0BAA0B,EAC1B,8BAA8B,EAC9B,qBAAqB,EACrB,yBAAyB,EACzB,8BAA8B,GAC/B,MAAM,kBAAkB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,EAClB,sBAAsB,EACtB,qBAAqB,GACtB,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAA;AACnD,OAAO,EAAE,yBAAyB,EAAE,MAAM,cAAc,CAAA;AACxD,YAAY,EAAE,yBAAyB,EAAE,MAAM,cAAc,CAAA;AAE7D,OAAO,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAA;AACpF,YAAY,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAA;AAE5D,YAAY,EACV,WAAW,EACX,mBAAmB,EACnB,kBAAkB,EAClB,qBAAqB,EACrB,2BAA2B,EAC3B,iBAAiB,EACjB,mBAAmB,EACnB,yBAAyB,EACzB,qBAAqB,GACtB,MAAM,YAAY,CAAA;AAEnB,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAA;AAC5E,YAAY,EACV,iBAAiB,EACjB,qBAAqB,EACrB,qBAAqB,EACrB,uBAAuB,GACxB,MAAM,uBAAuB,CAAA;AAE9B,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAA;AAC9E,YAAY,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAA;AAEjE,OAAO,EACL,0BAA0B,EAC1B,8BAA8B,EAC9B,qBAAqB,EACrB,yBAAyB,EACzB,8BAA8B,GAC/B,MAAM,kBAAkB,CAAA"}
package/dist/index.js CHANGED
@@ -2,5 +2,7 @@ export { integrationsPlugin, getIntegrationRegistry, getIntegrationContext, } fr
2
2
  export { IntegrationRegistry } from './registry.js';
3
3
  export { DEFAULT_INTEGRATIONS_SLUG } from './options.js';
4
4
  export { webhookIntegration, WEBHOOK_INTEGRATION_ID } from './integrations/index.js';
5
+ export { requestManualSync, MANUAL_SYNC_EVENT } from './sync/manual-sync.js';
6
+ export { createSyncEndpoint, ADMIN_TRIGGER_REASON } from './endpoints/sync.js';
5
7
  export { createListIntegrationsTool, createGetIntegrationStatusTool, createTriggerSyncTool, createTestIntegrationTool, createListIntegrationTypesTool, } from './tools/index.js';
6
8
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,EAClB,sBAAsB,EACtB,qBAAqB,GACtB,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAA;AACnD,OAAO,EAAE,yBAAyB,EAAE,MAAM,cAAc,CAAA;AAGxD,OAAO,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAA;AAepF,OAAO,EACL,0BAA0B,EAC1B,8BAA8B,EAC9B,qBAAqB,EACrB,yBAAyB,EACzB,8BAA8B,GAC/B,MAAM,kBAAkB,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,EAClB,sBAAsB,EACtB,qBAAqB,GACtB,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAA;AACnD,OAAO,EAAE,yBAAyB,EAAE,MAAM,cAAc,CAAA;AAGxD,OAAO,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAA;AAepF,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAA;AAQ5E,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAA;AAG9E,OAAO,EACL,0BAA0B,EAC1B,8BAA8B,EAC9B,qBAAqB,EACrB,yBAAyB,EACzB,8BAA8B,GAC/B,MAAM,kBAAkB,CAAA"}
package/dist/plugin.d.ts CHANGED
@@ -3,15 +3,19 @@ import { type IntegrationsPluginOptions } from './options.js';
3
3
  import { IntegrationRegistry } from './registry.js';
4
4
  import type { IntegrationContext } from './types.js';
5
5
  /**
6
- * Integrations server. Registers the Integrations collection, the
7
- * five-tool MCP server, and a process-local registry of integration
8
- * modules. The webhook integration is registered automatically; clients
6
+ * Integrations server. Registers the Integrations collection — with its
7
+ * manual-sync endpoint and the Sync now button in its sidebar — the five-tool
8
+ * MCP server, and a process-local registry of integration modules. The webhook integration is registered automatically; clients
9
9
  * extend the registry by passing additional integrations via options.
10
10
  *
11
11
  * Integration Inngest functions are not served here — they are exposed
12
12
  * via `getIntegrationRegistry(payload)` so the client app's Inngest
13
13
  * endpoint can merge them with its own functions. See
14
14
  * `docs/integrations-wiring.md` in the repository root.
15
+ *
16
+ * The collection carries one admin component, the sidebar's Sync now button,
17
+ * so hosts must run `payload generate:importmap` after adding this plugin (the
18
+ * dev server does it automatically). A stale import map 500s the admin screen.
15
19
  */
16
20
  export declare const integrationsPlugin: CorePlugin<IntegrationsPluginOptions>;
17
21
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAqB,MAAM,uCAAuC,CAAA;AAG1F,OAAO,EAAE,KAAK,yBAAyB,EAA8C,MAAM,cAAc,CAAA;AACzG,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAA;AAGnD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAgBpD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,kBAAkB,EAAE,UAAU,CAAC,yBAAyB,CA+HlE,CAAA;AAEH;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,sBAAsB,CAAC,EAAE,GAAG,OAAO,EACjD,OAAO,EAAE,OAAO,GACf,mBAAmB,CAAC,EAAE,CAAC,GAAG,SAAS,CAIrC;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,kBAAkB,GAAG,SAAS,CAEtF"}
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAqB,MAAM,uCAAuC,CAAA;AAG1F,OAAO,EAAE,KAAK,yBAAyB,EAA8C,MAAM,cAAc,CAAA;AACzG,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAA;AAInD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAgBpD;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,kBAAkB,EAAE,UAAU,CAAC,yBAAyB,CAsIlE,CAAA;AAEH;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,sBAAsB,CAAC,EAAE,GAAG,OAAO,EACjD,OAAO,EAAE,OAAO,GACf,mBAAmB,CAAC,EAAE,CAAC,GAAG,SAAS,CAIrC;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,kBAAkB,GAAG,SAAS,CAEtF"}
package/dist/plugin.js CHANGED
@@ -3,6 +3,7 @@ import { createNamedLogger, defaultLogger, getAuditWriter } from '@forumone/thro
3
3
  import { validateOptions, DEFAULT_INTEGRATIONS_SLUG } from './options.js';
4
4
  import { IntegrationRegistry } from './registry.js';
5
5
  import { createIntegrationsCollection } from './collection.js';
6
+ import { createSyncEndpoint } from './endpoints/sync.js';
6
7
  import { webhookIntegration } from './integrations/index.js';
7
8
  import { INTEGRATIONS_TOOL_DESCRIPTORS, createGetIntegrationStatusTool, createListIntegrationsTool, createListIntegrationTypesTool, createTestIntegrationTool, createTriggerSyncTool, } from './tools/index.js';
8
9
  const PLUGIN_ID = '@forumone/throughline-integrations';
@@ -10,15 +11,19 @@ const PLUGIN_VERSION = '0.1.0';
10
11
  const REGISTRY_SYMBOL = Symbol.for('@forumone/throughline/integrations-registry');
11
12
  const CONTEXT_SYMBOL = Symbol.for('@forumone/throughline/integrations-context');
12
13
  /**
13
- * Integrations server. Registers the Integrations collection, the
14
- * five-tool MCP server, and a process-local registry of integration
15
- * modules. The webhook integration is registered automatically; clients
14
+ * Integrations server. Registers the Integrations collection — with its
15
+ * manual-sync endpoint and the Sync now button in its sidebar — the five-tool
16
+ * MCP server, and a process-local registry of integration modules. The webhook integration is registered automatically; clients
16
17
  * extend the registry by passing additional integrations via options.
17
18
  *
18
19
  * Integration Inngest functions are not served here — they are exposed
19
20
  * via `getIntegrationRegistry(payload)` so the client app's Inngest
20
21
  * endpoint can merge them with its own functions. See
21
22
  * `docs/integrations-wiring.md` in the repository root.
23
+ *
24
+ * The collection carries one admin component, the sidebar's Sync now button,
25
+ * so hosts must run `payload generate:importmap` after adding this plugin (the
26
+ * dev server does it automatically). A stale import map 500s the admin screen.
22
27
  */
23
28
  export const integrationsPlugin = (rawOptions) => (incomingConfig) => {
24
29
  if (rawOptions.enabled === false)
@@ -31,7 +36,14 @@ export const integrationsPlugin = (rawOptions) => (incomingConfig) => {
31
36
  for (const integration of options.integrations ?? []) {
32
37
  registry.register(integration);
33
38
  }
34
- const collection = createIntegrationsCollection({ slug: collectionSlug, registry });
39
+ const collection = createIntegrationsCollection({
40
+ slug: collectionSlug,
41
+ registry,
42
+ // The admin's path to `integration/manual-sync`, sharing
43
+ // `requestManualSync` with the `trigger_sync` MCP tool so the two cannot
44
+ // disagree about what a trigger checks or what event it sends.
45
+ endpoints: [createSyncEndpoint({ collectionSlug, inngest: options.inngest })],
46
+ });
35
47
  /*
36
48
  Declared here, bound at `onInit` — `mcpPlugin` generates its per-key
37
49
  checkboxes from these names and descriptions while the config is built, and
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,64 @@
1
+ import type { Inngest } from 'inngest';
2
+ import type { Payload } from 'payload';
3
+ /**
4
+ * The event every integration's manual-sync function subscribes to. Fired for
5
+ * *all* integrations, which is why each handler filters the payload down to
6
+ * its own instance rather than trusting the event.
7
+ */
8
+ export declare const MANUAL_SYNC_EVENT = "integration/manual-sync";
9
+ export interface RequestManualSyncDeps {
10
+ payload: Payload;
11
+ collectionSlug: string;
12
+ inngest: Inngest;
13
+ }
14
+ export interface RequestManualSyncArgs {
15
+ /** Payload id of the instance to sync. */
16
+ instanceId: string;
17
+ /** Who asked. Recorded on the event so the run can be traced back to them. */
18
+ triggeredBy?: number | string | null;
19
+ /** Why. Carried on the event for the audit trail; not otherwise used. */
20
+ reason?: string | null;
21
+ }
22
+ /**
23
+ * Why a trigger was refused. Callers map these onto whatever their transport
24
+ * uses to say no — an error envelope for MCP, a status code for HTTP.
25
+ */
26
+ export type ManualSyncRefusal =
27
+ /** No instance with that id. */
28
+ 'not-found'
29
+ /** The instance exists but `enabled` is false. */
30
+ | 'disabled'
31
+ /** Inngest would not take the event. */
32
+ | 'send-failed';
33
+ export type RequestManualSyncResult = {
34
+ ok: true;
35
+ instanceId: string;
36
+ instanceName: string;
37
+ integrationType: string;
38
+ /**
39
+ * The instance's `lastSyncAt` at the moment the event was sent, or null
40
+ * if it has never run. A caller watching for the run to finish compares
41
+ * against this rather than against whatever it last rendered.
42
+ */
43
+ lastSyncAt: string | null;
44
+ } | {
45
+ ok: false;
46
+ code: ManualSyncRefusal;
47
+ message: string;
48
+ };
49
+ /**
50
+ * Sends `integration/manual-sync` for one instance, having checked that the
51
+ * instance exists and is enabled.
52
+ *
53
+ * This is the single definition of what triggering a sync *means*, shared by
54
+ * the `trigger_sync` MCP tool and the admin endpoint behind the Sync now
55
+ * button. It deliberately does not check permissions: MCP and HTTP authenticate
56
+ * differently and each caller applies `admin`-only itself, before calling.
57
+ *
58
+ * It fires an event and returns. The sync runs in Inngest and finishes later —
59
+ * an `ok` result means the run was *requested*, never that it succeeded. The
60
+ * outcome lands on the instance's `lastSyncAt` / `lastSyncStatus` / `lastError`
61
+ * when the run completes.
62
+ */
63
+ export declare function requestManualSync(deps: RequestManualSyncDeps, args: RequestManualSyncArgs): Promise<RequestManualSyncResult>;
64
+ //# sourceMappingURL=manual-sync.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manual-sync.d.ts","sourceRoot":"","sources":["../../src/sync/manual-sync.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AACtC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AAEtC;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,4BAA4B,CAAA;AAE1D,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,OAAO,CAAA;IAChB,cAAc,EAAE,MAAM,CAAA;IACtB,OAAO,EAAE,OAAO,CAAA;CACjB;AAED,MAAM,WAAW,qBAAqB;IACpC,0CAA0C;IAC1C,UAAU,EAAE,MAAM,CAAA;IAClB,8EAA8E;IAC9E,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;IACpC,yEAAyE;IACzE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACvB;AAED;;;GAGG;AACH,MAAM,MAAM,iBAAiB;AAC3B,gCAAgC;AAC9B,WAAW;AACb,kDAAkD;GAChD,UAAU;AACZ,wCAAwC;GACtC,aAAa,CAAA;AAEjB,MAAM,MAAM,uBAAuB,GAC/B;IACE,EAAE,EAAE,IAAI,CAAA;IACR,UAAU,EAAE,MAAM,CAAA;IAClB,YAAY,EAAE,MAAM,CAAA;IACpB,eAAe,EAAE,MAAM,CAAA;IACvB;;;;OAIG;IACH,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;CAC1B,GACD;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,iBAAiB,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAA;AAE3D;;;;;;;;;;;;;GAaG;AACH,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,qBAAqB,EAC3B,IAAI,EAAE,qBAAqB,GAC1B,OAAO,CAAC,uBAAuB,CAAC,CA0ElC"}
@@ -0,0 +1,88 @@
1
+ /**
2
+ * The event every integration's manual-sync function subscribes to. Fired for
3
+ * *all* integrations, which is why each handler filters the payload down to
4
+ * its own instance rather than trusting the event.
5
+ */
6
+ export const MANUAL_SYNC_EVENT = 'integration/manual-sync';
7
+ /**
8
+ * Sends `integration/manual-sync` for one instance, having checked that the
9
+ * instance exists and is enabled.
10
+ *
11
+ * This is the single definition of what triggering a sync *means*, shared by
12
+ * the `trigger_sync` MCP tool and the admin endpoint behind the Sync now
13
+ * button. It deliberately does not check permissions: MCP and HTTP authenticate
14
+ * differently and each caller applies `admin`-only itself, before calling.
15
+ *
16
+ * It fires an event and returns. The sync runs in Inngest and finishes later —
17
+ * an `ok` result means the run was *requested*, never that it succeeded. The
18
+ * outcome lands on the instance's `lastSyncAt` / `lastSyncStatus` / `lastError`
19
+ * when the run completes.
20
+ */
21
+ export async function requestManualSync(deps, args) {
22
+ let doc = null;
23
+ try {
24
+ doc = (await deps.payload.findByID({
25
+ collection: deps.collectionSlug,
26
+ id: args.instanceId,
27
+ }));
28
+ }
29
+ catch {
30
+ doc = null;
31
+ }
32
+ if (!doc) {
33
+ return {
34
+ ok: false,
35
+ code: 'not-found',
36
+ message: `No integration instance with id "${args.instanceId}".`,
37
+ };
38
+ }
39
+ const instanceName = String(doc['name']);
40
+ if (doc['enabled'] !== true) {
41
+ return {
42
+ ok: false,
43
+ code: 'disabled',
44
+ message: `Integration "${instanceName}" is disabled. Enable it in the admin before triggering.`,
45
+ };
46
+ }
47
+ const integrationType = String(doc['integrationType']);
48
+ try {
49
+ await deps.inngest.send({
50
+ name: MANUAL_SYNC_EVENT,
51
+ data: {
52
+ integrationId: integrationType,
53
+ instanceId: args.instanceId,
54
+ triggeredBy: args.triggeredBy ?? null,
55
+ reason: args.reason ?? null,
56
+ },
57
+ });
58
+ }
59
+ catch (error) {
60
+ /*
61
+ A failed send used to escape as an unhandled rejection, which the MCP
62
+ transport turned into a generic tool error and a button would have turned
63
+ into nothing at all — the worst outcome, because a sync that was never
64
+ requested looks exactly like one that has not finished yet.
65
+ */
66
+ return {
67
+ ok: false,
68
+ code: 'send-failed',
69
+ message: `Could not reach Inngest to queue the sync: ${error instanceof Error ? error.message : String(error)}`,
70
+ };
71
+ }
72
+ // A `date` field comes back as an ISO string over REST and as a Date from
73
+ // the local API, and this function is called from both.
74
+ const rawLastSyncAt = doc['lastSyncAt'];
75
+ const lastSyncAt = rawLastSyncAt instanceof Date
76
+ ? rawLastSyncAt.toISOString()
77
+ : typeof rawLastSyncAt === 'string'
78
+ ? rawLastSyncAt
79
+ : null;
80
+ return {
81
+ ok: true,
82
+ instanceId: args.instanceId,
83
+ instanceName,
84
+ integrationType,
85
+ lastSyncAt,
86
+ };
87
+ }
88
+ //# sourceMappingURL=manual-sync.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manual-sync.js","sourceRoot":"","sources":["../../src/sync/manual-sync.ts"],"names":[],"mappings":"AAGA;;;;GAIG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,yBAAyB,CAAA;AA4C1D;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,IAA2B,EAC3B,IAA2B;IAE3B,IAAI,GAAG,GAAmC,IAAI,CAAA;IAC9C,IAAI,CAAC;QACH,GAAG,GAAG,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC;YACjC,UAAU,EAAE,IAAI,CAAC,cAAc;YAC/B,EAAE,EAAE,IAAI,CAAC,UAAU;SACpB,CAAC,CAAmC,CAAA;IACvC,CAAC;IAAC,MAAM,CAAC;QACP,GAAG,GAAG,IAAI,CAAA;IACZ,CAAC;IAED,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,OAAO;YACL,EAAE,EAAE,KAAK;YACT,IAAI,EAAE,WAAW;YACjB,OAAO,EAAE,oCAAoC,IAAI,CAAC,UAAU,IAAI;SACjE,CAAA;IACH,CAAC;IAED,MAAM,YAAY,GAAG,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAA;IAExC,IAAI,GAAG,CAAC,SAAS,CAAC,KAAK,IAAI,EAAE,CAAC;QAC5B,OAAO;YACL,EAAE,EAAE,KAAK;YACT,IAAI,EAAE,UAAU;YAChB,OAAO,EAAE,gBAAgB,YAAY,0DAA0D;SAChG,CAAA;IACH,CAAC;IAED,MAAM,eAAe,GAAG,MAAM,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC,CAAA;IAEtD,IAAI,CAAC;QACH,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC;YACtB,IAAI,EAAE,iBAAiB;YACvB,IAAI,EAAE;gBACJ,aAAa,EAAE,eAAe;gBAC9B,UAAU,EAAE,IAAI,CAAC,UAAU;gBAC3B,WAAW,EAAE,IAAI,CAAC,WAAW,IAAI,IAAI;gBACrC,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,IAAI;aAC5B;SACF,CAAC,CAAA;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf;;;;;UAKE;QACF,OAAO;YACL,EAAE,EAAE,KAAK;YACT,IAAI,EAAE,aAAa;YACnB,OAAO,EAAE,8CACP,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CACvD,EAAE;SACH,CAAA;IACH,CAAC;IAED,0EAA0E;IAC1E,wDAAwD;IACxD,MAAM,aAAa,GAAG,GAAG,CAAC,YAAY,CAAC,CAAA;IACvC,MAAM,UAAU,GACd,aAAa,YAAY,IAAI;QAC3B,CAAC,CAAC,aAAa,CAAC,WAAW,EAAE;QAC7B,CAAC,CAAC,OAAO,aAAa,KAAK,QAAQ;YACjC,CAAC,CAAC,aAAa;YACf,CAAC,CAAC,IAAI,CAAA;IAEZ,OAAO;QACL,EAAE,EAAE,IAAI;QACR,UAAU,EAAE,IAAI,CAAC,UAAU;QAC3B,YAAY;QACZ,eAAe;QACf,UAAU;KACX,CAAA;AACH,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"trigger-sync.d.ts","sourceRoot":"","sources":["../../src/tools/trigger-sync.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AACtC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AACtC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uCAAuC,CAAA;AAI9E,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,OAAO,CAAA;IAChB,cAAc,EAAE,MAAM,CAAA;IACtB,OAAO,EAAE,OAAO,CAAA;CACjB;AAED,QAAA,MAAM,WAAW;;;;;;;;;EAQf,CAAA;AAEF,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,eAAe,GACpB,iBAAiB,CAAC,OAAO,WAAW,CAAC,CAkDvC"}
1
+ {"version":3,"file":"trigger-sync.d.ts","sourceRoot":"","sources":["../../src/tools/trigger-sync.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AACtC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AACtC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uCAAuC,CAAA;AAK9E,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,OAAO,CAAA;IAChB,cAAc,EAAE,MAAM,CAAA;IACtB,OAAO,EAAE,OAAO,CAAA;CACjB;AAED,QAAA,MAAM,WAAW;;;;;;;;;EAQf,CAAA;AAEF,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,eAAe,GACpB,iBAAiB,CAAC,OAAO,WAAW,CAAC,CA+BvC"}
@@ -1,6 +1,7 @@
1
1
  import { z } from 'zod';
2
2
  import { deniedEnvelope, isIntegrationsAdmin } from './access.js';
3
3
  import { INTEGRATIONS_TOOLS } from './descriptors.js';
4
+ import { requestManualSync } from '../sync/manual-sync.js';
4
5
  const inputSchema = z.object({
5
6
  integrationId: z
6
7
  .string()
@@ -19,41 +20,23 @@ export function createTriggerSyncTool(deps) {
19
20
  if (!isIntegrationsAdmin(ctx)) {
20
21
  return deniedEnvelope('Only admins can manually trigger integrations.');
21
22
  }
22
- let doc = null;
23
- try {
24
- doc = (await deps.payload.findByID({
25
- collection: deps.collectionSlug,
26
- id: input.integrationId,
27
- }));
28
- }
29
- catch {
30
- doc = null;
31
- }
32
- if (!doc)
33
- return { error: `No integration instance with id "${input.integrationId}".` };
34
- if (doc['enabled'] !== true) {
35
- return {
36
- error: `Integration "${String(doc['name'])}" is disabled. Enable it in the admin before triggering.`,
37
- };
38
- }
39
- const integrationType = String(doc['integrationType']);
40
- await deps.inngest.send({
41
- name: 'integration/manual-sync',
42
- data: {
43
- integrationId: integrationType,
44
- instanceId: input.integrationId,
45
- triggeredBy: ctx.user?.id ?? null,
46
- reason: input.reason ?? null,
47
- },
23
+ // The rules — instance exists, instance enabled, this event shape — live
24
+ // in `requestManualSync`, which the admin's Sync now button calls too.
25
+ const result = await requestManualSync(deps, {
26
+ instanceId: input.integrationId,
27
+ triggeredBy: ctx.user?.id ?? null,
28
+ reason: input.reason ?? null,
48
29
  });
30
+ if (!result.ok)
31
+ return { error: result.message };
49
32
  return {
50
33
  ok: true,
51
34
  triggered: {
52
- instanceId: input.integrationId,
53
- instanceName: String(doc['name']),
54
- type: integrationType,
35
+ instanceId: result.instanceId,
36
+ instanceName: result.instanceName,
37
+ type: result.integrationType,
55
38
  },
56
- message: `Triggered manual sync for "${String(doc['name'])}". Check list_integrations or get_integration_status in a few seconds for the result.`,
39
+ message: `Triggered manual sync for "${result.instanceName}". Check list_integrations or get_integration_status in a few seconds for the result.`,
57
40
  };
58
41
  },
59
42
  };
@@ -1 +1 @@
1
- {"version":3,"file":"trigger-sync.js","sourceRoot":"","sources":["../../src/tools/trigger-sync.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAIvB,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AACjE,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AAQrD,MAAM,WAAW,GAAG,CAAC,CAAC,MAAM,CAAC;IAC3B,aAAa,EAAE,CAAC;SACb,MAAM,EAAE;SACR,QAAQ,CAAC,4DAA4D,CAAC;IACzE,MAAM,EAAE,CAAC;SACN,MAAM,EAAE;SACR,QAAQ,EAAE;SACV,QAAQ,CAAC,2DAA2D,CAAC;CACzE,CAAC,CAAA;AAEF,MAAM,UAAU,qBAAqB,CACnC,IAAqB;IAErB,OAAO;QACL,GAAG,kBAAkB,CAAC,WAAW;QACjC,aAAa,EAAE,sBAAsB;QACrC,WAAW;QACX,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;YAC5B,IAAI,CAAC,mBAAmB,CAAC,GAAG,CAAC,EAAE,CAAC;gBAC9B,OAAO,cAAc,CAAC,gDAAgD,CAAC,CAAA;YACzE,CAAC;YAED,IAAI,GAAG,GAAmC,IAAI,CAAA;YAC9C,IAAI,CAAC;gBACH,GAAG,GAAG,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC;oBACjC,UAAU,EAAE,IAAI,CAAC,cAAc;oBAC/B,EAAE,EAAE,KAAK,CAAC,aAAa;iBACxB,CAAC,CAAmC,CAAA;YACvC,CAAC;YAAC,MAAM,CAAC;gBACP,GAAG,GAAG,IAAI,CAAA;YACZ,CAAC;YAED,IAAI,CAAC,GAAG;gBAAE,OAAO,EAAE,KAAK,EAAE,oCAAoC,KAAK,CAAC,aAAa,IAAI,EAAE,CAAA;YACvF,IAAI,GAAG,CAAC,SAAS,CAAC,KAAK,IAAI,EAAE,CAAC;gBAC5B,OAAO;oBACL,KAAK,EAAE,gBAAgB,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,0DAA0D;iBACrG,CAAA;YACH,CAAC;YAED,MAAM,eAAe,GAAG,MAAM,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC,CAAA;YAEtD,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC;gBACtB,IAAI,EAAE,yBAAyB;gBAC/B,IAAI,EAAE;oBACJ,aAAa,EAAE,eAAe;oBAC9B,UAAU,EAAE,KAAK,CAAC,aAAa;oBAC/B,WAAW,EAAE,GAAG,CAAC,IAAI,EAAE,EAAE,IAAI,IAAI;oBACjC,MAAM,EAAE,KAAK,CAAC,MAAM,IAAI,IAAI;iBAC7B;aACF,CAAC,CAAA;YAEF,OAAO;gBACL,EAAE,EAAE,IAAI;gBACR,SAAS,EAAE;oBACT,UAAU,EAAE,KAAK,CAAC,aAAa;oBAC/B,YAAY,EAAE,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;oBACjC,IAAI,EAAE,eAAe;iBACtB;gBACD,OAAO,EAAE,8BAA8B,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,uFAAuF;aAClJ,CAAA;QACH,CAAC;KACF,CAAA;AACH,CAAC"}
1
+ {"version":3,"file":"trigger-sync.js","sourceRoot":"","sources":["../../src/tools/trigger-sync.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAIvB,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AACjE,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AACrD,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAQ1D,MAAM,WAAW,GAAG,CAAC,CAAC,MAAM,CAAC;IAC3B,aAAa,EAAE,CAAC;SACb,MAAM,EAAE;SACR,QAAQ,CAAC,4DAA4D,CAAC;IACzE,MAAM,EAAE,CAAC;SACN,MAAM,EAAE;SACR,QAAQ,EAAE;SACV,QAAQ,CAAC,2DAA2D,CAAC;CACzE,CAAC,CAAA;AAEF,MAAM,UAAU,qBAAqB,CACnC,IAAqB;IAErB,OAAO;QACL,GAAG,kBAAkB,CAAC,WAAW;QACjC,aAAa,EAAE,sBAAsB;QACrC,WAAW;QACX,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;YAC5B,IAAI,CAAC,mBAAmB,CAAC,GAAG,CAAC,EAAE,CAAC;gBAC9B,OAAO,cAAc,CAAC,gDAAgD,CAAC,CAAA;YACzE,CAAC;YAED,yEAAyE;YACzE,uEAAuE;YACvE,MAAM,MAAM,GAAG,MAAM,iBAAiB,CAAC,IAAI,EAAE;gBAC3C,UAAU,EAAE,KAAK,CAAC,aAAa;gBAC/B,WAAW,EAAE,GAAG,CAAC,IAAI,EAAE,EAAE,IAAI,IAAI;gBACjC,MAAM,EAAE,KAAK,CAAC,MAAM,IAAI,IAAI;aAC7B,CAAC,CAAA;YAEF,IAAI,CAAC,MAAM,CAAC,EAAE;gBAAE,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,OAAO,EAAE,CAAA;YAEhD,OAAO;gBACL,EAAE,EAAE,IAAI;gBACR,SAAS,EAAE;oBACT,UAAU,EAAE,MAAM,CAAC,UAAU;oBAC7B,YAAY,EAAE,MAAM,CAAC,YAAY;oBACjC,IAAI,EAAE,MAAM,CAAC,eAAe;iBAC7B;gBACD,OAAO,EAAE,8BAA8B,MAAM,CAAC,YAAY,uFAAuF;aAClJ,CAAA;QACH,CAAC;KACF,CAAA;AACH,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forumone/throughline-integrations",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "Plugin architecture for connecting Throughline-powered Payload sites to external systems. Ships the Integration contract, registry, collection, MCP tools, and a generic outbound-webhook integration as the first concrete example.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -9,6 +9,10 @@
9
9
  ".": {
10
10
  "types": "./dist/index.d.ts",
11
11
  "default": "./dist/index.js"
12
+ },
13
+ "./client": {
14
+ "types": "./dist/exports/client.d.ts",
15
+ "default": "./dist/exports/client.js"
12
16
  }
13
17
  },
14
18
  "files": [
@@ -37,19 +41,32 @@
37
41
  "access": "public"
38
42
  },
39
43
  "peerDependencies": {
44
+ "@payloadcms/ui": "^3.0.0",
40
45
  "inngest": "^4.0.0",
41
- "payload": "^3.0.0"
46
+ "payload": "^3.0.0",
47
+ "react": "^18.0.0 || ^19.0.0"
48
+ },
49
+ "peerDependenciesMeta": {
50
+ "@payloadcms/ui": {
51
+ "optional": true
52
+ },
53
+ "react": {
54
+ "optional": true
55
+ }
42
56
  },
43
57
  "dependencies": {
44
58
  "zod": "^3.23.0",
45
- "@forumone/throughline-core": "0.8.0",
46
- "@forumone/throughline-plugin-contract": "0.4.0"
59
+ "@forumone/throughline-plugin-contract": "0.4.0",
60
+ "@forumone/throughline-core": "0.8.0"
47
61
  },
48
62
  "devDependencies": {
63
+ "@payloadcms/ui": "^3.83.0",
49
64
  "@types/node": "^20.17.0",
65
+ "@types/react": "^19.2.0",
50
66
  "eslint": "^9.15.0",
51
67
  "inngest": "^4.2.0",
52
68
  "payload": "^3.83.0",
69
+ "react": "^19.2.0",
53
70
  "typescript": "^5.6.0",
54
71
  "vitest": "^2.1.0",
55
72
  "@forumone/throughline-eslint-config": "0.0.0",