@xyne/workflow-sdk 3.2.36 → 3.2.38
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/dist/agents/agent-step.d.ts +2 -2
- package/dist/agents/host-agent-step.d.ts +1 -1
- package/dist/builder/index.d.ts +2 -2
- package/dist/builder/index.d.ts.map +1 -1
- package/dist/builder/index.js +1 -1
- package/dist/builder/index.js.map +1 -1
- package/dist/client/events-client.d.ts +57 -0
- package/dist/client/events-client.d.ts.map +1 -0
- package/dist/client/events-client.js +107 -0
- package/dist/client/events-client.js.map +1 -0
- package/dist/client/index.d.ts +2 -0
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/sse.d.ts +30 -0
- package/dist/client/sse.d.ts.map +1 -0
- package/dist/client/sse.js +76 -0
- package/dist/client/sse.js.map +1 -0
- package/dist/client/types.d.ts +31 -6
- package/dist/client/types.d.ts.map +1 -1
- package/dist/client/url.d.ts +11 -0
- package/dist/client/url.d.ts.map +1 -0
- package/dist/client/url.js +28 -0
- package/dist/client/url.js.map +1 -0
- package/dist/client/workflow-client.d.ts +1 -1
- package/dist/client/workflow-client.d.ts.map +1 -1
- package/dist/client/workflow-client.js +32 -20
- package/dist/client/workflow-client.js.map +1 -1
- package/dist/common/citation-ref.d.ts +4 -4
- package/dist/common/credentials.d.ts +107 -0
- package/dist/common/credentials.d.ts.map +1 -1
- package/dist/common/credentials.js +67 -0
- package/dist/common/credentials.js.map +1 -1
- package/dist/common/index.d.ts +2 -2
- package/dist/common/index.d.ts.map +1 -1
- package/dist/common/index.js +2 -2
- package/dist/common/index.js.map +1 -1
- package/dist/connectors/builtin/builtin-connector.d.ts +32 -0
- package/dist/connectors/builtin/builtin-connector.d.ts.map +1 -0
- package/dist/connectors/builtin/builtin-connector.js +62 -0
- package/dist/connectors/builtin/builtin-connector.js.map +1 -0
- package/dist/connectors/builtin/credentials.d.ts +18 -0
- package/dist/connectors/builtin/credentials.d.ts.map +1 -0
- package/dist/connectors/builtin/credentials.js +50 -0
- package/dist/connectors/builtin/credentials.js.map +1 -0
- package/dist/connectors/builtin/steps/code.step.d.ts +99 -0
- package/dist/connectors/builtin/steps/code.step.d.ts.map +1 -0
- package/dist/connectors/builtin/steps/code.step.js +152 -0
- package/dist/connectors/builtin/steps/code.step.js.map +1 -0
- package/dist/connectors/builtin/steps/conditional.step.d.ts +182 -0
- package/dist/connectors/builtin/steps/conditional.step.d.ts.map +1 -0
- package/dist/connectors/builtin/steps/conditional.step.js +82 -0
- package/dist/connectors/builtin/steps/conditional.step.js.map +1 -0
- package/dist/connectors/builtin/steps/dedup.step.d.ts +65 -0
- package/dist/connectors/builtin/steps/dedup.step.d.ts.map +1 -0
- package/dist/connectors/builtin/steps/dedup.step.js +61 -0
- package/dist/connectors/builtin/steps/dedup.step.js.map +1 -0
- package/dist/connectors/builtin/steps/http-request.step.d.ts +1027 -0
- package/dist/connectors/builtin/steps/http-request.step.d.ts.map +1 -0
- package/dist/connectors/builtin/steps/http-request.step.js +497 -0
- package/dist/connectors/builtin/steps/http-request.step.js.map +1 -0
- package/dist/connectors/builtin/steps/loop.step.d.ts +100 -0
- package/dist/connectors/builtin/steps/loop.step.d.ts.map +1 -0
- package/dist/connectors/builtin/steps/loop.step.js +73 -0
- package/dist/connectors/builtin/steps/loop.step.js.map +1 -0
- package/dist/connectors/builtin/steps/map.step.d.ts +148 -0
- package/dist/connectors/builtin/steps/map.step.d.ts.map +1 -0
- package/dist/connectors/builtin/steps/map.step.js +111 -0
- package/dist/connectors/builtin/steps/map.step.js.map +1 -0
- package/dist/connectors/builtin/steps/parallel.step.d.ts +246 -0
- package/dist/connectors/builtin/steps/parallel.step.d.ts.map +1 -0
- package/dist/connectors/builtin/steps/parallel.step.js +175 -0
- package/dist/connectors/builtin/steps/parallel.step.js.map +1 -0
- package/dist/connectors/builtin/steps/ssrf-guard.d.ts +9 -0
- package/dist/connectors/builtin/steps/ssrf-guard.d.ts.map +1 -0
- package/dist/connectors/builtin/steps/ssrf-guard.js +115 -0
- package/dist/connectors/builtin/steps/ssrf-guard.js.map +1 -0
- package/dist/connectors/builtin/steps/switch.step.d.ts +235 -0
- package/dist/connectors/builtin/steps/switch.step.d.ts.map +1 -0
- package/dist/connectors/builtin/steps/switch.step.js +101 -0
- package/dist/connectors/builtin/steps/switch.step.js.map +1 -0
- package/dist/connectors/builtin/steps/wait.step.d.ts +1072 -0
- package/dist/connectors/builtin/steps/wait.step.d.ts.map +1 -0
- package/dist/connectors/builtin/steps/wait.step.js +254 -0
- package/dist/connectors/builtin/steps/wait.step.js.map +1 -0
- package/dist/connectors/builtin/triggers/default-cron-trigger.d.ts +44 -0
- package/dist/connectors/builtin/triggers/default-cron-trigger.d.ts.map +1 -0
- package/dist/connectors/builtin/triggers/default-cron-trigger.js +31 -0
- package/dist/connectors/builtin/triggers/default-cron-trigger.js.map +1 -0
- package/dist/connectors/builtin/triggers/default-event-trigger.d.ts +32 -0
- package/dist/connectors/builtin/triggers/default-event-trigger.d.ts.map +1 -0
- package/dist/connectors/builtin/triggers/default-event-trigger.js +21 -0
- package/dist/connectors/builtin/triggers/default-event-trigger.js.map +1 -0
- package/dist/connectors/builtin/triggers/default-manual-trigger.d.ts +45 -0
- package/dist/connectors/builtin/triggers/default-manual-trigger.d.ts.map +1 -0
- package/dist/connectors/builtin/triggers/default-manual-trigger.js +51 -0
- package/dist/connectors/builtin/triggers/default-manual-trigger.js.map +1 -0
- package/dist/connectors/builtin/triggers/default-webhook-v2-trigger.d.ts +31 -0
- package/dist/connectors/builtin/triggers/default-webhook-v2-trigger.d.ts.map +1 -0
- package/dist/connectors/builtin/triggers/default-webhook-v2-trigger.js +40 -0
- package/dist/connectors/builtin/triggers/default-webhook-v2-trigger.js.map +1 -0
- package/dist/connectors/core/base-connector.d.ts +45 -0
- package/dist/connectors/core/base-connector.d.ts.map +1 -0
- package/dist/connectors/core/base-connector.js +3 -0
- package/dist/connectors/core/base-connector.js.map +1 -0
- package/dist/connectors/core/connector-registry.d.ts +130 -0
- package/dist/connectors/core/connector-registry.d.ts.map +1 -0
- package/dist/connectors/core/connector-registry.js +357 -0
- package/dist/connectors/core/connector-registry.js.map +1 -0
- package/dist/connectors/core/credentials.d.ts +104 -0
- package/dist/connectors/core/credentials.d.ts.map +1 -0
- package/dist/connectors/core/credentials.js +67 -0
- package/dist/connectors/core/credentials.js.map +1 -0
- package/dist/connectors/core/index.d.ts +7 -0
- package/dist/connectors/core/index.d.ts.map +1 -0
- package/dist/connectors/core/index.js +5 -0
- package/dist/connectors/core/index.js.map +1 -0
- package/dist/engine/available-context.d.ts +3 -4
- package/dist/engine/available-context.d.ts.map +1 -1
- package/dist/engine/available-context.js +17 -13
- package/dist/engine/available-context.js.map +1 -1
- package/dist/engine/config-validator.d.ts +13 -5
- package/dist/engine/config-validator.d.ts.map +1 -1
- package/dist/engine/config-validator.js +89 -11
- package/dist/engine/config-validator.js.map +1 -1
- package/dist/engine/credential-resolver.d.ts +42 -0
- package/dist/engine/credential-resolver.d.ts.map +1 -0
- package/dist/engine/credential-resolver.js +63 -0
- package/dist/engine/credential-resolver.js.map +1 -0
- package/dist/engine/workflow-executor.d.ts +15 -9
- package/dist/engine/workflow-executor.d.ts.map +1 -1
- package/dist/engine/workflow-executor.js +31 -40
- package/dist/engine/workflow-executor.js.map +1 -1
- package/dist/events/in-memory-adapter.d.ts +15 -0
- package/dist/events/in-memory-adapter.d.ts.map +1 -0
- package/dist/events/in-memory-adapter.js +50 -0
- package/dist/events/in-memory-adapter.js.map +1 -0
- package/dist/events/types.d.ts +154 -0
- package/dist/events/types.d.ts.map +1 -0
- package/dist/events/types.js +34 -0
- package/dist/events/types.js.map +1 -0
- package/dist/index.d.ts +50 -31
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +39 -16
- package/dist/index.js.map +1 -1
- package/dist/ingest/body.d.ts +18 -0
- package/dist/ingest/body.d.ts.map +1 -0
- package/dist/ingest/body.js +47 -0
- package/dist/ingest/body.js.map +1 -0
- package/dist/ingest/filter.d.ts +11 -0
- package/dist/ingest/filter.d.ts.map +1 -0
- package/dist/ingest/filter.js +23 -0
- package/dist/ingest/filter.js.map +1 -0
- package/dist/ingest/headers.d.ts +3 -0
- package/dist/ingest/headers.d.ts.map +1 -0
- package/dist/ingest/headers.js +11 -0
- package/dist/ingest/headers.js.map +1 -0
- package/dist/ingest/types.d.ts +50 -0
- package/dist/ingest/types.d.ts.map +1 -0
- package/dist/ingest/types.js +9 -0
- package/dist/ingest/types.js.map +1 -0
- package/dist/persistence/in-memory-adapter.d.ts +4 -8
- package/dist/persistence/in-memory-adapter.d.ts.map +1 -1
- package/dist/persistence/in-memory-adapter.js +8 -25
- package/dist/persistence/in-memory-adapter.js.map +1 -1
- package/dist/persistence/types.d.ts +5 -19
- package/dist/persistence/types.d.ts.map +1 -1
- package/dist/router/types.d.ts +31 -3
- package/dist/router/types.d.ts.map +1 -1
- package/dist/router/workflow-router.d.ts +4 -3
- package/dist/router/workflow-router.d.ts.map +1 -1
- package/dist/router/workflow-router.js +258 -63
- package/dist/router/workflow-router.js.map +1 -1
- package/dist/runtime/types.d.ts +36 -56
- package/dist/runtime/types.d.ts.map +1 -1
- package/dist/runtime/workflow-runtime.d.ts +70 -46
- package/dist/runtime/workflow-runtime.d.ts.map +1 -1
- package/dist/runtime/workflow-runtime.js +217 -141
- package/dist/runtime/workflow-runtime.js.map +1 -1
- package/dist/steps/base-step.d.ts +23 -0
- package/dist/steps/base-step.d.ts.map +1 -1
- package/dist/steps/base-step.js +22 -0
- package/dist/steps/base-step.js.map +1 -1
- package/dist/steps/step-descriptor.d.ts +52 -0
- package/dist/steps/step-descriptor.d.ts.map +1 -0
- package/dist/steps/step-descriptor.js +32 -0
- package/dist/steps/step-descriptor.js.map +1 -0
- package/dist/testing/authz-conformance.d.ts.map +1 -1
- package/dist/testing/authz-conformance.js +6 -7
- package/dist/testing/authz-conformance.js.map +1 -1
- package/dist/testing/index.d.ts +1 -0
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +1 -0
- package/dist/testing/index.js.map +1 -1
- package/dist/testing/mock-step-context.d.ts +1 -0
- package/dist/testing/mock-step-context.d.ts.map +1 -1
- package/dist/testing/mock-step-context.js +1 -0
- package/dist/testing/mock-step-context.js.map +1 -1
- package/dist/testing/test-connector.d.ts +40 -0
- package/dist/testing/test-connector.d.ts.map +1 -0
- package/dist/testing/test-connector.js +39 -0
- package/dist/testing/test-connector.js.map +1 -0
- package/dist/triggers/api-trigger.d.ts +15 -0
- package/dist/triggers/api-trigger.d.ts.map +1 -0
- package/dist/triggers/api-trigger.js +14 -0
- package/dist/triggers/api-trigger.js.map +1 -0
- package/dist/triggers/base-trigger.d.ts +29 -0
- package/dist/triggers/base-trigger.d.ts.map +1 -1
- package/dist/triggers/base-trigger.js +16 -0
- package/dist/triggers/base-trigger.js.map +1 -1
- package/dist/triggers/trigger-descriptor.d.ts +42 -0
- package/dist/triggers/trigger-descriptor.d.ts.map +1 -0
- package/dist/triggers/trigger-descriptor.js +29 -0
- package/dist/triggers/trigger-descriptor.js.map +1 -0
- package/dist/triggers/webhook-trigger.d.ts +31 -38
- package/dist/triggers/webhook-trigger.d.ts.map +1 -1
- package/dist/triggers/webhook-trigger.js +19 -6
- package/dist/triggers/webhook-trigger.js.map +1 -1
- package/dist/types/known-types.d.ts +0 -1
- package/dist/types/known-types.d.ts.map +1 -1
- package/dist/types/known-types.js +0 -1
- package/dist/types/known-types.js.map +1 -1
- package/dist/types/validation.d.ts +3 -1
- package/dist/types/validation.d.ts.map +1 -1
- package/dist/types/validation.js +2 -0
- package/dist/types/validation.js.map +1 -1
- package/dist/types/workflow-config.d.ts +23 -0
- package/dist/types/workflow-config.d.ts.map +1 -1
- package/dist/types/workflow-config.js +8 -0
- package/dist/types/workflow-config.js.map +1 -1
- package/dist/util/credential-slot-schema.d.ts +20 -0
- package/dist/util/credential-slot-schema.d.ts.map +1 -0
- package/dist/util/credential-slot-schema.js +30 -0
- package/dist/util/credential-slot-schema.js.map +1 -0
- package/dist/util/event-channel.d.ts +25 -0
- package/dist/util/event-channel.d.ts.map +1 -0
- package/dist/util/event-channel.js +61 -0
- package/dist/util/event-channel.js.map +1 -0
- package/package.json +1 -1
- package/dist/common/expression-eval.d.ts +0 -35
- package/dist/common/expression-eval.d.ts.map +0 -1
- package/dist/common/expression-eval.js +0 -214
- package/dist/common/expression-eval.js.map +0 -1
- package/dist/common/principal.d.ts +0 -45
- package/dist/common/principal.d.ts.map +0 -1
- package/dist/common/principal.js +0 -9
- package/dist/common/principal.js.map +0 -1
- package/dist/steps/builtin/transform.step.d.ts +0 -247
- package/dist/steps/builtin/transform.step.d.ts.map +0 -1
- package/dist/steps/builtin/transform.step.js +0 -135
- package/dist/steps/builtin/transform.step.js.map +0 -1
- package/dist/types/attachment.d.ts +0 -23
- package/dist/types/attachment.d.ts.map +0 -1
- package/dist/types/attachment.js +0 -2
- package/dist/types/attachment.js.map +0 -1
- package/dist/types/resume-payload.d.ts +0 -34
- package/dist/types/resume-payload.d.ts.map +0 -1
- package/dist/types/resume-payload.js +0 -12
- package/dist/types/resume-payload.js.map +0 -1
- package/dist/util/executable-check.d.ts +0 -42
- package/dist/util/executable-check.d.ts.map +0 -1
- package/dist/util/executable-check.js +0 -115
- package/dist/util/executable-check.js.map +0 -1
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* createWorkflowRouter — framework-agnostic route definitions.
|
|
3
3
|
*
|
|
4
4
|
* Returns an array of `RouteDefinition` objects the host maps to their
|
|
5
|
-
* HTTP framework. Each definition has: method, path, handler, and
|
|
6
|
-
*
|
|
5
|
+
* HTTP framework. Each definition has: method, path, handler, and its
|
|
6
|
+
* `access` class, which says who authenticates the caller.
|
|
7
7
|
*
|
|
8
8
|
* Usage with Express:
|
|
9
9
|
* ```ts
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
* const handler = async (req, res) => {
|
|
15
15
|
* const result = await route.handler(
|
|
16
16
|
* { params: req.params, query: req.query, body: req.body, headers: req.headers },
|
|
17
|
-
*
|
|
17
|
+
* // The signed-in user; null for `provider` routes.
|
|
18
|
+
* route.access === 'provider' ? null : await authenticate(req.headers),
|
|
18
19
|
* );
|
|
19
20
|
* res.status(result.status).json(result.body);
|
|
20
21
|
* };
|
|
@@ -27,8 +28,9 @@ import { createChatAgentTools } from '../agents/builtin/chat-tools.js';
|
|
|
27
28
|
import { buildWorkflowContext, buildExecutionDataFile, collectExecutionAttachments, EXECUTION_DATA_PATH } from './approval-context.js';
|
|
28
29
|
import { OPERATOR_DESCRIPTORS } from '../types/operators.js';
|
|
29
30
|
import { getAvailableContext } from '../engine/available-context.js';
|
|
31
|
+
import { executionTopic, workflowTopic } from '../events/types.js';
|
|
30
32
|
import { isExecutable } from '../common/executable-check.js';
|
|
31
|
-
import { createEventChannel } from '../
|
|
33
|
+
import { createEventChannel } from '../util/event-channel.js';
|
|
32
34
|
import { AccessDeniedError, ResourceNotFoundError } from '../authz/default.js';
|
|
33
35
|
import { applyResolvedCredentialAuth, CredentialNameSchema, CredentialAuthSchema, validateCredentialValues, } from '../common/credentials.js';
|
|
34
36
|
// ─── Helpers ───
|
|
@@ -131,6 +133,54 @@ async function buildMultipartTriggerPayload(req, storage, scope, inputSchema) {
|
|
|
131
133
|
function formatSSE(event, data) {
|
|
132
134
|
return `event: ${event}\ndata: ${JSON.stringify(data)}\n\n`;
|
|
133
135
|
}
|
|
136
|
+
/**
|
|
137
|
+
* An SSE comment. Carries no event, so it never reaches a client handler — it
|
|
138
|
+
* exists purely so that something crosses the wire on an idle connection, before
|
|
139
|
+
* a proxy or load balancer decides the stream is dead and closes it.
|
|
140
|
+
*/
|
|
141
|
+
const SSE_KEEPALIVE = ': keepalive\n\n';
|
|
142
|
+
/** How long an event stream may sit silent before it sends a keepalive. */
|
|
143
|
+
const KEEPALIVE_INTERVAL_MS = 25_000;
|
|
144
|
+
/**
|
|
145
|
+
* Marker pushed through an event channel to trigger a keepalive. A symbol so it
|
|
146
|
+
* can never be mistaken for — or collide with — a real event.
|
|
147
|
+
*/
|
|
148
|
+
const KEEPALIVE = Symbol('keepalive');
|
|
149
|
+
/** Ceiling on topics per multiplexed connection. */
|
|
150
|
+
const MAX_STREAM_TOPICS = 100;
|
|
151
|
+
/**
|
|
152
|
+
* The writer's self-declared identity, read from `X-Workflow-Origin` and echoed
|
|
153
|
+
* back on the change event the mutation emits.
|
|
154
|
+
*
|
|
155
|
+
* A header rather than a body field: it describes the call, not the resource, so
|
|
156
|
+
* it works the same on JSON, multipart and empty-bodied routes without any of
|
|
157
|
+
* them having to make room for it in their schema.
|
|
158
|
+
*
|
|
159
|
+
* Untrusted by construction — a client may send whatever it likes. Nothing is
|
|
160
|
+
* authorized on it; it only ever lets a client recognise the echo of its own
|
|
161
|
+
* write. Truncated so a subscriber cannot be made to carry an unbounded string.
|
|
162
|
+
*/
|
|
163
|
+
function readOrigin(req) {
|
|
164
|
+
const raw = req.headers['x-workflow-origin'];
|
|
165
|
+
const value = Array.isArray(raw) ? raw[0] : raw;
|
|
166
|
+
return value ? { origin: value.slice(0, 128) } : {};
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Parse an id list from a query parameter, accepting both `?a=1,2` and `?a=1&a=2`
|
|
170
|
+
* so the route does not depend on how the host's framework folds repeats.
|
|
171
|
+
*/
|
|
172
|
+
function parseIdList(raw) {
|
|
173
|
+
const parts = Array.isArray(raw) ? raw : typeof raw === 'string' ? [raw] : [];
|
|
174
|
+
const ids = new Set();
|
|
175
|
+
for (const part of parts) {
|
|
176
|
+
for (const id of part.split(',')) {
|
|
177
|
+
const trimmed = id.trim();
|
|
178
|
+
if (trimmed)
|
|
179
|
+
ids.add(trimmed);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return [...ids];
|
|
183
|
+
}
|
|
134
184
|
// ─── Router Factory ───
|
|
135
185
|
export function createWorkflowRouter(
|
|
136
186
|
// `any` for services: the router never touches TServices, only forwards ctx.
|
|
@@ -139,9 +189,9 @@ runtime,
|
|
|
139
189
|
// `authenticate` is invoked by the host glue, not the factory (see file header).
|
|
140
190
|
_opts) {
|
|
141
191
|
const routes = [];
|
|
142
|
-
//
|
|
143
|
-
//
|
|
144
|
-
//
|
|
192
|
+
// `session` handlers always receive a resolved ctx — the host authenticates
|
|
193
|
+
// before dispatch. `provider` routes never forward `auth` to a guarded runtime
|
|
194
|
+
// call, so this narrowing is sound.
|
|
145
195
|
const asCtx = (auth) => auth;
|
|
146
196
|
// ─── Workflow CRUD ───
|
|
147
197
|
routes.push({
|
|
@@ -200,7 +250,7 @@ _opts) {
|
|
|
200
250
|
summary: typeof body['summary'] === 'string' ? body['summary'] : null,
|
|
201
251
|
folderId: body['folderId'],
|
|
202
252
|
attributes: body['attributes'],
|
|
203
|
-
});
|
|
253
|
+
}, readOrigin(req));
|
|
204
254
|
return created(workflow);
|
|
205
255
|
}
|
|
206
256
|
catch (err) {
|
|
@@ -233,7 +283,7 @@ _opts) {
|
|
|
233
283
|
}
|
|
234
284
|
if (typeof body['folderId'] === 'string')
|
|
235
285
|
input.folderId = body['folderId'];
|
|
236
|
-
const workflow = await runtime.updateWorkflow(asCtx(auth), id, input);
|
|
286
|
+
const workflow = await runtime.updateWorkflow(asCtx(auth), id, input, readOrigin(req));
|
|
237
287
|
return ok(workflow);
|
|
238
288
|
}
|
|
239
289
|
catch (err) {
|
|
@@ -363,7 +413,7 @@ _opts) {
|
|
|
363
413
|
if (folders.length === 0 && workflows.length === 0)
|
|
364
414
|
return badRequest('Nothing to move');
|
|
365
415
|
try {
|
|
366
|
-
await runtime.moveBatch(asCtx(auth), { folders, workflows });
|
|
416
|
+
await runtime.moveBatch(asCtx(auth), { folders, workflows }, readOrigin(req));
|
|
367
417
|
return ok({ moved: folders.length + workflows.length });
|
|
368
418
|
}
|
|
369
419
|
catch (err) {
|
|
@@ -395,7 +445,7 @@ _opts) {
|
|
|
395
445
|
if (!id)
|
|
396
446
|
return badRequest('Missing workflow ID');
|
|
397
447
|
try {
|
|
398
|
-
await runtime.activateWorkflow(asCtx(auth), id);
|
|
448
|
+
await runtime.activateWorkflow(asCtx(auth), id, readOrigin(req));
|
|
399
449
|
return ok({ activated: true });
|
|
400
450
|
}
|
|
401
451
|
catch (err) {
|
|
@@ -413,7 +463,7 @@ _opts) {
|
|
|
413
463
|
if (!id)
|
|
414
464
|
return badRequest('Missing workflow ID');
|
|
415
465
|
try {
|
|
416
|
-
await runtime.deactivateWorkflow(asCtx(auth), id);
|
|
466
|
+
await runtime.deactivateWorkflow(asCtx(auth), id, readOrigin(req));
|
|
417
467
|
return ok({ deactivated: true });
|
|
418
468
|
}
|
|
419
469
|
catch (err) {
|
|
@@ -449,9 +499,11 @@ _opts) {
|
|
|
449
499
|
routes.push({
|
|
450
500
|
method: 'POST',
|
|
451
501
|
path: '/v2/workflows/:workflowId/trigger/v2',
|
|
452
|
-
|
|
502
|
+
// No session. The SDK does not authenticate this caller; a host that exposes
|
|
503
|
+
// the route guards it, for example with an API key.
|
|
504
|
+
access: 'provider',
|
|
453
505
|
multipart: 'any',
|
|
454
|
-
handler: async (req
|
|
506
|
+
handler: async (req) => {
|
|
455
507
|
const workflowId = req.params['workflowId'];
|
|
456
508
|
if (!workflowId)
|
|
457
509
|
return badRequest('Missing workflow ID');
|
|
@@ -468,13 +520,15 @@ _opts) {
|
|
|
468
520
|
}
|
|
469
521
|
}
|
|
470
522
|
try {
|
|
471
|
-
const
|
|
472
|
-
const
|
|
473
|
-
const
|
|
474
|
-
const executionId = await runtime.triggerWebhookV2Public(workflowId, payload);
|
|
523
|
+
const target = await runtime.getApiTriggerTarget(workflowId);
|
|
524
|
+
const payload = await buildMultipartTriggerPayload(req, runtime.storage, target.storageScope, target.inputSchema);
|
|
525
|
+
const executionId = await runtime.triggerViaApi(workflowId, payload);
|
|
475
526
|
return created({ executionId });
|
|
476
527
|
}
|
|
477
528
|
catch (err) {
|
|
529
|
+
// An unknown workflow goes to the uniform 404 mapping at the end of this file.
|
|
530
|
+
if (err instanceof ResourceNotFoundError)
|
|
531
|
+
throw err;
|
|
478
532
|
if (err instanceof Error && err.message.includes('not found')) {
|
|
479
533
|
return notFound(err.message);
|
|
480
534
|
}
|
|
@@ -707,34 +761,36 @@ _opts) {
|
|
|
707
761
|
return ok({ topErrors });
|
|
708
762
|
},
|
|
709
763
|
});
|
|
710
|
-
// ─── Webhooks (no
|
|
764
|
+
// ─── Webhooks (no session — the trigger's own verify authenticates the sender) ───
|
|
711
765
|
routes.push({
|
|
712
766
|
method: 'POST',
|
|
713
|
-
path: '/webhooks/:workflowId
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
767
|
+
path: '/webhooks/:workflowId',
|
|
768
|
+
access: 'provider',
|
|
769
|
+
rawBody: true,
|
|
770
|
+
handler: async (req) => {
|
|
771
|
+
if (!req.rawBody) {
|
|
772
|
+
// A signature covers the bytes as sent, so a parsed body cannot be
|
|
773
|
+
// verified. Refuse rather than silently skip authentication.
|
|
774
|
+
return error(500, 'Webhook route requires the raw request body; the host must honour RouteDefinition.rawBody');
|
|
775
|
+
}
|
|
776
|
+
const { reply } = await runtime.handleWebhook(req.params['workflowId'] ?? '', {
|
|
777
|
+
method: 'POST',
|
|
719
778
|
headers: req.headers,
|
|
720
|
-
body: req.body,
|
|
721
779
|
query: req.query,
|
|
780
|
+
rawBody: req.rawBody,
|
|
722
781
|
});
|
|
723
|
-
|
|
724
|
-
status:
|
|
725
|
-
body:
|
|
782
|
+
return {
|
|
783
|
+
status: reply.status,
|
|
784
|
+
body: reply.body,
|
|
785
|
+
...(reply.headers ? { headers: { ...reply.headers } } : {}),
|
|
726
786
|
};
|
|
727
|
-
if (result.httpResponse.headers) {
|
|
728
|
-
response.headers = result.httpResponse.headers;
|
|
729
|
-
}
|
|
730
|
-
return response;
|
|
731
787
|
},
|
|
732
788
|
});
|
|
733
789
|
// ─── Wait callbacks (no auth — validated by workflow secret in path) ───
|
|
734
790
|
routes.push({
|
|
735
791
|
method: 'POST',
|
|
736
792
|
path: '/wait/callback/:secret/:executionId',
|
|
737
|
-
|
|
793
|
+
access: 'provider',
|
|
738
794
|
handler: async (req, _auth) => {
|
|
739
795
|
const secret = req.params['secret'] ?? '';
|
|
740
796
|
const executionId = req.params['executionId'] ?? '';
|
|
@@ -743,12 +799,19 @@ _opts) {
|
|
|
743
799
|
},
|
|
744
800
|
});
|
|
745
801
|
// ─── Schema metadata (for UI) ───
|
|
802
|
+
routes.push({
|
|
803
|
+
method: 'GET',
|
|
804
|
+
path: '/schema/credentials',
|
|
805
|
+
handler: async () => {
|
|
806
|
+
return ok({ connectors: runtime.connectors.listCredentialTypes() });
|
|
807
|
+
},
|
|
808
|
+
});
|
|
746
809
|
routes.push({
|
|
747
810
|
method: 'GET',
|
|
748
811
|
path: '/schema/triggers',
|
|
749
812
|
handler: async () => {
|
|
750
813
|
return ok({
|
|
751
|
-
triggers: runtime.
|
|
814
|
+
triggers: runtime.connectors.listTriggerSummaries(),
|
|
752
815
|
});
|
|
753
816
|
},
|
|
754
817
|
});
|
|
@@ -757,7 +820,7 @@ _opts) {
|
|
|
757
820
|
path: '/schema/steps',
|
|
758
821
|
handler: async () => {
|
|
759
822
|
return ok({
|
|
760
|
-
steps: runtime.
|
|
823
|
+
steps: runtime.connectors.listStepSummaries(),
|
|
761
824
|
});
|
|
762
825
|
},
|
|
763
826
|
});
|
|
@@ -769,10 +832,10 @@ _opts) {
|
|
|
769
832
|
const type = req.params['type'];
|
|
770
833
|
if (!type)
|
|
771
834
|
return badRequest('Missing trigger type');
|
|
772
|
-
if (!runtime.
|
|
835
|
+
if (!runtime.connectors.hasTrigger(type)) {
|
|
773
836
|
return notFound(`Unknown trigger type "${type}"`);
|
|
774
837
|
}
|
|
775
|
-
return ok(runtime.
|
|
838
|
+
return ok(runtime.connectors.getTriggerDescriptor(type));
|
|
776
839
|
},
|
|
777
840
|
});
|
|
778
841
|
routes.push({
|
|
@@ -782,10 +845,10 @@ _opts) {
|
|
|
782
845
|
const type = req.params['type'];
|
|
783
846
|
if (!type)
|
|
784
847
|
return badRequest('Missing step type');
|
|
785
|
-
if (!runtime.
|
|
848
|
+
if (!runtime.connectors.hasStep(type)) {
|
|
786
849
|
return notFound(`Unknown step type "${type}"`);
|
|
787
850
|
}
|
|
788
|
-
return ok(runtime.
|
|
851
|
+
return ok(runtime.connectors.getStepDescriptor(type));
|
|
789
852
|
},
|
|
790
853
|
});
|
|
791
854
|
routes.push({
|
|
@@ -806,7 +869,7 @@ _opts) {
|
|
|
806
869
|
}
|
|
807
870
|
const config = body['config'];
|
|
808
871
|
const atStepId = body['atStepId'];
|
|
809
|
-
const context = getAvailableContext(config, atStepId, runtime.
|
|
872
|
+
const context = getAvailableContext(config, atStepId, runtime.connectors);
|
|
810
873
|
return ok({ context });
|
|
811
874
|
},
|
|
812
875
|
});
|
|
@@ -909,8 +972,9 @@ _opts) {
|
|
|
909
972
|
},
|
|
910
973
|
});
|
|
911
974
|
// ── POST /credentials ──
|
|
912
|
-
// Create a new credential
|
|
913
|
-
//
|
|
975
|
+
// Create a new credential of a connector-declared type. The shape comes from
|
|
976
|
+
// the type's definition, never from the client, and values are validated
|
|
977
|
+
// strictly against it.
|
|
914
978
|
routes.push({
|
|
915
979
|
method: 'POST',
|
|
916
980
|
path: '/credentials',
|
|
@@ -929,15 +993,23 @@ _opts) {
|
|
|
929
993
|
if (!nameResult.success) {
|
|
930
994
|
return badRequest('Invalid or missing "name"');
|
|
931
995
|
}
|
|
996
|
+
const credType = body['credType'];
|
|
997
|
+
const definition = typeof credType === 'string'
|
|
998
|
+
? runtime.connectors.findCredentialDefinition(credType)
|
|
999
|
+
: undefined;
|
|
1000
|
+
if (!definition) {
|
|
1001
|
+
return badRequest('Invalid or missing "credType"');
|
|
1002
|
+
}
|
|
932
1003
|
const credentialAuthResult = CredentialAuthSchema.safeParse({
|
|
933
|
-
authType:
|
|
1004
|
+
authType: definition.authType,
|
|
934
1005
|
values: body['values'],
|
|
935
1006
|
});
|
|
936
1007
|
if (!credentialAuthResult.success) {
|
|
937
|
-
return badRequest(
|
|
1008
|
+
return badRequest(`Invalid "values" for credential type "${definition.credType}"`);
|
|
938
1009
|
}
|
|
939
1010
|
const input = {
|
|
940
1011
|
name: nameResult.data,
|
|
1012
|
+
credType: definition.credType,
|
|
941
1013
|
...credentialAuthResult.data,
|
|
942
1014
|
};
|
|
943
1015
|
try {
|
|
@@ -1113,6 +1185,99 @@ _opts) {
|
|
|
1113
1185
|
}
|
|
1114
1186
|
},
|
|
1115
1187
|
});
|
|
1188
|
+
// ─── SSE: Multiplexed Change Stream ───
|
|
1189
|
+
//
|
|
1190
|
+
// One connection, many topics: `GET /events?workflows=a,b`.
|
|
1191
|
+
//
|
|
1192
|
+
// The topic set lives in the URL and nowhere else, which is what keeps this
|
|
1193
|
+
// working behind a load balancer with no session affinity. There is no
|
|
1194
|
+
// server-side subscription to mutate, so a client that wants to watch something
|
|
1195
|
+
// new simply reconnects with a different query string — and any pod can serve
|
|
1196
|
+
// it, because every pod subscribes to whatever its own clients asked for.
|
|
1197
|
+
//
|
|
1198
|
+
// Frames are notifications, not payloads: `workflow_changed` says *that* a
|
|
1199
|
+
// workflow changed, and the client re-reads through the ordinary authorized
|
|
1200
|
+
// GET. Only `workflows` is accepted today — a live execution has its own richer
|
|
1201
|
+
// stream at `/executions/:execId/stream`, which patches step state in place
|
|
1202
|
+
// rather than re-reading, and duplicating it here would give it two meanings.
|
|
1203
|
+
// The query shape is keyed by entity so further families slot in unchanged.
|
|
1204
|
+
routes.push({
|
|
1205
|
+
method: 'GET',
|
|
1206
|
+
path: '/events',
|
|
1207
|
+
handler: async (req, auth) => {
|
|
1208
|
+
const eventBus = runtime.eventBus;
|
|
1209
|
+
if (!eventBus) {
|
|
1210
|
+
return error(501, 'Event streaming not configured');
|
|
1211
|
+
}
|
|
1212
|
+
const requested = parseIdList(req.query['workflows']);
|
|
1213
|
+
if (requested.length === 0) {
|
|
1214
|
+
return badRequest('Specify at least one id, e.g. ?workflows=a,b');
|
|
1215
|
+
}
|
|
1216
|
+
if (requested.length > MAX_STREAM_TOPICS) {
|
|
1217
|
+
return badRequest(`Too many topics (${String(requested.length)}); the limit is ${String(MAX_STREAM_TOPICS)}`);
|
|
1218
|
+
}
|
|
1219
|
+
// Authorize every id up front, through the same read the client would do.
|
|
1220
|
+
// A bus is addressed by resource id and knows nothing about principals, so
|
|
1221
|
+
// this is the only place the caller's access is actually decided.
|
|
1222
|
+
//
|
|
1223
|
+
// One unreadable id does not fail the connection: on a multiplexed stream
|
|
1224
|
+
// that would take down every other topic the client is watching, and access
|
|
1225
|
+
// can be lost mid-session. The `ready` frame reports what was accepted, so
|
|
1226
|
+
// the client is never left believing it is watching something it is not.
|
|
1227
|
+
const subscribed = [];
|
|
1228
|
+
const rejected = [];
|
|
1229
|
+
await Promise.all(requested.map(async (id) => {
|
|
1230
|
+
try {
|
|
1231
|
+
await runtime.getWorkflow(asCtx(auth), id);
|
|
1232
|
+
subscribed.push(id);
|
|
1233
|
+
}
|
|
1234
|
+
catch {
|
|
1235
|
+
rejected.push(id);
|
|
1236
|
+
}
|
|
1237
|
+
}));
|
|
1238
|
+
const channel = createEventChannel();
|
|
1239
|
+
const results = await Promise.allSettled(subscribed.map((id) => eventBus.subscribe(workflowTopic(id), channel.push)));
|
|
1240
|
+
const unsubs = results.flatMap((r) => r.status === 'fulfilled' ? [r.value] : []);
|
|
1241
|
+
if (unsubs.length !== results.length) {
|
|
1242
|
+
// A partially-subscribed stream is worse than none: it looks live and is
|
|
1243
|
+
// silently blind to some of its topics. Tear it down and let the client
|
|
1244
|
+
// reconnect.
|
|
1245
|
+
for (const unsub of unsubs)
|
|
1246
|
+
unsub();
|
|
1247
|
+
channel.end();
|
|
1248
|
+
return error(503, 'Could not open the event stream');
|
|
1249
|
+
}
|
|
1250
|
+
const keepalive = setInterval(() => {
|
|
1251
|
+
channel.push(KEEPALIVE);
|
|
1252
|
+
}, KEEPALIVE_INTERVAL_MS);
|
|
1253
|
+
async function* generateSSE() {
|
|
1254
|
+
try {
|
|
1255
|
+
// Sent before anything else so the client knows the subscription is
|
|
1256
|
+
// live and which topics it actually got.
|
|
1257
|
+
yield formatSSE('ready', { subscribed, rejected });
|
|
1258
|
+
for await (const frame of channel) {
|
|
1259
|
+
yield frame === KEEPALIVE ? SSE_KEEPALIVE : formatSSE(frame.event, frame.data);
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
finally {
|
|
1263
|
+
clearInterval(keepalive);
|
|
1264
|
+
for (const unsub of unsubs)
|
|
1265
|
+
unsub();
|
|
1266
|
+
channel.end();
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
return {
|
|
1270
|
+
status: 200,
|
|
1271
|
+
body: null,
|
|
1272
|
+
headers: {
|
|
1273
|
+
'Content-Type': 'text/event-stream',
|
|
1274
|
+
'Cache-Control': 'no-cache',
|
|
1275
|
+
'Connection': 'keep-alive',
|
|
1276
|
+
},
|
|
1277
|
+
stream: generateSSE(),
|
|
1278
|
+
};
|
|
1279
|
+
},
|
|
1280
|
+
});
|
|
1116
1281
|
// ─── SSE: Execution Event Stream ───
|
|
1117
1282
|
routes.push({
|
|
1118
1283
|
method: 'GET',
|
|
@@ -1125,27 +1290,51 @@ _opts) {
|
|
|
1125
1290
|
if (!eventBus) {
|
|
1126
1291
|
return error(501, 'Event streaming not configured');
|
|
1127
1292
|
}
|
|
1128
|
-
|
|
1129
|
-
|
|
1293
|
+
// Subscribe BEFORE reading the snapshot, not after.
|
|
1294
|
+
//
|
|
1295
|
+
// Anything published in between is then delivered twice — once folded into
|
|
1296
|
+
// the snapshot, once live — which costs a redundant repaint, because every
|
|
1297
|
+
// execution event is idempotent. A miss is not recoverable: drop
|
|
1298
|
+
// `execution_completed` and the client sits on an open connection waiting
|
|
1299
|
+
// for a frame that has already been and gone. Subscribing second is a race
|
|
1300
|
+
// even in-process; against a distributed bus, where subscribe is a network
|
|
1301
|
+
// round trip, it is a race you lose regularly.
|
|
1302
|
+
//
|
|
1303
|
+
// Ordering it this way subscribes fractionally before the caller's access is
|
|
1304
|
+
// checked. Nothing is delivered in that window: every failure path below
|
|
1305
|
+
// tears the subscription down and drops the channel unread, and the first
|
|
1306
|
+
// frame is only ever yielded after `getExecutionDetail` has authorized it.
|
|
1307
|
+
const channel = createEventChannel();
|
|
1308
|
+
const unsub = await eventBus.subscribe(executionTopic(execId), channel.push);
|
|
1309
|
+
const found = await runtime
|
|
1310
|
+
.getExecutionDetail(asCtx(auth), execId)
|
|
1311
|
+
.catch((err) => {
|
|
1312
|
+
unsub();
|
|
1313
|
+
throw err;
|
|
1314
|
+
});
|
|
1315
|
+
if (!found) {
|
|
1316
|
+
unsub();
|
|
1130
1317
|
return notFound(`Execution ${execId} not found`);
|
|
1318
|
+
}
|
|
1319
|
+
// Re-bound after the guard: `generateSSE` is a hoisted declaration, so TS
|
|
1320
|
+
// will not carry the null-narrowing of `found` into it.
|
|
1321
|
+
const detail = found;
|
|
1131
1322
|
const isTerminal = ['COMPLETED', 'FAILED', 'CANCELLED'].includes(detail.execution.status);
|
|
1132
1323
|
async function* generateSSE() {
|
|
1133
|
-
// 1. Send initial snapshot
|
|
1134
|
-
yield formatSSE('snapshot', {
|
|
1135
|
-
...detail.execution,
|
|
1136
|
-
steps: detail.steps,
|
|
1137
|
-
config: detail.config,
|
|
1138
|
-
triggerData: detail.triggerData,
|
|
1139
|
-
summary: detail.summary,
|
|
1140
|
-
name: detail.name,
|
|
1141
|
-
});
|
|
1142
|
-
// 2. If already terminal, close immediately
|
|
1143
|
-
if (isTerminal)
|
|
1144
|
-
return;
|
|
1145
|
-
// 3. Subscribe to live events
|
|
1146
|
-
const channel = createEventChannel();
|
|
1147
|
-
const unsub = eventBus.subscribe(execId, channel.push);
|
|
1148
1324
|
try {
|
|
1325
|
+
// 1. Send the initial snapshot (buffered events wait their turn).
|
|
1326
|
+
yield formatSSE('snapshot', {
|
|
1327
|
+
...detail.execution,
|
|
1328
|
+
steps: detail.steps,
|
|
1329
|
+
config: detail.config,
|
|
1330
|
+
triggerData: detail.triggerData,
|
|
1331
|
+
summary: detail.summary,
|
|
1332
|
+
name: detail.name,
|
|
1333
|
+
});
|
|
1334
|
+
// 2. If already terminal, close immediately.
|
|
1335
|
+
if (isTerminal)
|
|
1336
|
+
return;
|
|
1337
|
+
// 3. Drain live events.
|
|
1149
1338
|
for await (const event of channel) {
|
|
1150
1339
|
yield formatSSE(event.event, event.data);
|
|
1151
1340
|
// Close stream on terminal events
|
|
@@ -1157,7 +1346,10 @@ _opts) {
|
|
|
1157
1346
|
}
|
|
1158
1347
|
}
|
|
1159
1348
|
finally {
|
|
1349
|
+
// Runs however the generator ends — terminal event, client disconnect,
|
|
1350
|
+
// or a throw mid-yield.
|
|
1160
1351
|
unsub();
|
|
1352
|
+
channel.end();
|
|
1161
1353
|
}
|
|
1162
1354
|
}
|
|
1163
1355
|
return {
|
|
@@ -1255,7 +1447,7 @@ _opts) {
|
|
|
1255
1447
|
routes.push({
|
|
1256
1448
|
method: 'POST',
|
|
1257
1449
|
path: '/ai-builder/chat',
|
|
1258
|
-
|
|
1450
|
+
access: 'session',
|
|
1259
1451
|
handler: async (req, auth) => {
|
|
1260
1452
|
const body = req.body;
|
|
1261
1453
|
if (!body.messages || !Array.isArray(body.messages)) {
|
|
@@ -1453,6 +1645,9 @@ _opts) {
|
|
|
1453
1645
|
// than a 500 — and so a new route cannot forget to do it.
|
|
1454
1646
|
return routes.map((route) => ({
|
|
1455
1647
|
...route,
|
|
1648
|
+
// A route that does not declare its access needs a signed-in user, so
|
|
1649
|
+
// forgetting to classify one never mounts it open.
|
|
1650
|
+
access: route.access ?? 'session',
|
|
1456
1651
|
handler: async (req, auth) => {
|
|
1457
1652
|
try {
|
|
1458
1653
|
return await route.handler(req, auth);
|