@revenexx/integrations-node-sdk 1.8.1 → 1.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/conditions.d.ts +16 -0
- package/dist/credentialType.d.ts +17 -0
- package/dist/credentials.d.ts +162 -0
- package/dist/errors.d.ts +5 -0
- package/dist/extract.d.ts +5 -0
- package/dist/fetch.d.ts +111 -0
- package/dist/images.d.ts +28 -0
- package/dist/index.cjs +17 -1
- package/dist/index.d.ts +14 -1254
- package/dist/index.js +17 -1
- package/dist/localized.d.ts +15 -0
- package/dist/manifest.d.ts +83 -0
- package/dist/nodeErrors.d.ts +61 -0
- package/dist/operators.d.ts +27 -0
- package/dist/retry.d.ts +75 -0
- package/dist/ssrf.d.ts +88 -0
- package/dist/types.d.ts +620 -0
- package/package.json +4 -4
- package/dist/index.d.cts +0 -1254
package/dist/index.d.cts
DELETED
|
@@ -1,1254 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The comparison vocabulary, in one place because it is read in three:
|
|
3
|
-
* a node that compares two values (a condition, a filter), a settings condition
|
|
4
|
-
* saying when a field applies (`IConfigField.showIf`), and the editor drawing
|
|
5
|
-
* that field. An author who has met these words in one of the three must not
|
|
6
|
-
* have to learn a second set for the others.
|
|
7
|
-
*
|
|
8
|
-
* The names are the vocabulary; what each one *means* is {@link evaluate}, and
|
|
9
|
-
* the answer table in `operators.test.ts` is what a second implementation of it
|
|
10
|
-
* — the editor, the platform's workflow validator — is checked against.
|
|
11
|
-
*/
|
|
12
|
-
declare const OPERATORS: readonly ["equals", "notEquals", "contains", "notContains", "startsWith", "endsWith", "greaterThan", "greaterThanOrEqual", "lessThan", "lessThanOrEqual", "exists", "notExists", "isEmpty", "isNotEmpty"];
|
|
13
|
-
type Operator = (typeof OPERATORS)[number];
|
|
14
|
-
/** The four that read whether a value is there rather than what it is. */
|
|
15
|
-
declare const VALUELESS_OPERATORS: readonly Operator[];
|
|
16
|
-
declare function isOperator(value: unknown): value is Operator;
|
|
17
|
-
/** Whether an operator reads a second value at all, or only the first. */
|
|
18
|
-
declare function takesValue(op: Operator): boolean;
|
|
19
|
-
/**
|
|
20
|
-
* What an operator means.
|
|
21
|
-
*
|
|
22
|
-
* Equality is deliberately lenient across the string/number line: a `select`
|
|
23
|
-
* carries `1` where a text box carries `'1'`, and an author means one thing by
|
|
24
|
-
* both. A second implementation has to reproduce that — and has to be careful
|
|
25
|
-
* with booleans, whose default stringification differs between languages.
|
|
26
|
-
*/
|
|
27
|
-
declare function evaluate(left: unknown, op: Operator, right: unknown): boolean;
|
|
28
|
-
|
|
29
|
-
type LocalizedString = string | Record<string, string>;
|
|
30
|
-
type DataType = 'any' | 'object' | 'array' | 'string' | 'number' | 'boolean';
|
|
31
|
-
type OutputKind = 'default' | 'branch' | 'error';
|
|
32
|
-
type NodeCategory = 'trigger' | 'action' | 'transform' | 'control' | 'io';
|
|
33
|
-
type ConfigType = 'string' | 'number' | 'boolean' | 'select' | 'multiselect' | 'object' | 'array' | 'expression' | 'secret-ref' | 'credentials-ref' | 'state-ref' | 'dynamic-schema';
|
|
34
|
-
/**
|
|
35
|
-
* Semantic category of an image, driving how the registry/CDN organises and
|
|
36
|
-
* displays it.
|
|
37
|
-
*/
|
|
38
|
-
type ImageCategory = 'screenshot' | 'logo' | 'banner' | 'icon' | 'other';
|
|
39
|
-
/**
|
|
40
|
-
* A single image a node, credential type, or template ships alongside its
|
|
41
|
-
* description (e.g. a screenshot, logo, or banner). The file is bundled into
|
|
42
|
-
* the package tarball and uploaded to the Revenexx CDN when the package is
|
|
43
|
-
* published.
|
|
44
|
-
*/
|
|
45
|
-
interface IImage {
|
|
46
|
-
/** Path to the image file, relative to the package root (e.g. `images/screenshot.png`). */
|
|
47
|
-
src: string;
|
|
48
|
-
alt: LocalizedString;
|
|
49
|
-
title?: LocalizedString;
|
|
50
|
-
category: ImageCategory;
|
|
51
|
-
}
|
|
52
|
-
interface IInputPort {
|
|
53
|
-
dataType: DataType;
|
|
54
|
-
required?: boolean;
|
|
55
|
-
description?: LocalizedString;
|
|
56
|
-
}
|
|
57
|
-
/**
|
|
58
|
-
* One value an output carries, as the editor offers it to the next step.
|
|
59
|
-
*
|
|
60
|
-
* Deliberately flat: there is no member describing what is inside the value,
|
|
61
|
-
* because the registry's manifest schema refuses any key beside these three and
|
|
62
|
-
* the studio reads none. What is inside a value is declared beside it, under a
|
|
63
|
-
* dotted name — see `IOutputPort.fields` (PO-408).
|
|
64
|
-
*/
|
|
65
|
-
interface IOutputField {
|
|
66
|
-
dataType: DataType;
|
|
67
|
-
/**
|
|
68
|
-
* What the value is called, for an author picking it in the editor's `{ }` list —
|
|
69
|
-
* "Rechnungen" where the key is `invoices` (PO-594). Left out, the editor shows the
|
|
70
|
-
* key, as before.
|
|
71
|
-
*/
|
|
72
|
-
label?: LocalizedString;
|
|
73
|
-
description?: LocalizedString;
|
|
74
|
-
}
|
|
75
|
-
interface IOutputPort {
|
|
76
|
-
kind: OutputKind;
|
|
77
|
-
dataType: DataType;
|
|
78
|
-
name?: string;
|
|
79
|
-
label?: LocalizedString;
|
|
80
|
-
description?: LocalizedString;
|
|
81
|
-
/**
|
|
82
|
-
* The values this output carries, by name.
|
|
83
|
-
*
|
|
84
|
-
* A name containing `.` is a path into the value: `counts` as `object` and
|
|
85
|
-
* `counts.added` as `number`, side by side, offers both the object and the
|
|
86
|
-
* number inside it. The studio and the worker resolve that name as a path, so
|
|
87
|
-
* the record the node emits must really be nested — a record carrying a
|
|
88
|
-
* literal `"counts.added"` key is not reached by it (specs/output-fields.md).
|
|
89
|
-
*/
|
|
90
|
-
fields?: Record<string, IOutputField>;
|
|
91
|
-
sourceFromConfig?: string;
|
|
92
|
-
/**
|
|
93
|
-
* Marks a generic port set resolved at author time by the node's
|
|
94
|
-
* `resolveOutputs` callback (PO-143), rather than a static `name` or a
|
|
95
|
-
* config-driven `sourceFromConfig`. Set at most one of `name`,
|
|
96
|
-
* `sourceFromConfig`, `resolveOutputs` — this is a server-validated
|
|
97
|
-
* constraint on publish, not enforced by this (intentionally flat) type.
|
|
98
|
-
*/
|
|
99
|
-
resolveOutputs?: boolean;
|
|
100
|
-
fallback?: {
|
|
101
|
-
name: string;
|
|
102
|
-
label?: LocalizedString;
|
|
103
|
-
description?: LocalizedString;
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
interface IConfigOption {
|
|
107
|
-
value: string | number | boolean;
|
|
108
|
-
label: LocalizedString;
|
|
109
|
-
}
|
|
110
|
-
interface IConfigValidation {
|
|
111
|
-
pattern?: string;
|
|
112
|
-
min?: number;
|
|
113
|
-
max?: number;
|
|
114
|
-
minLength?: number;
|
|
115
|
-
maxLength?: number;
|
|
116
|
-
}
|
|
117
|
-
/**
|
|
118
|
-
* A condition on another setting's value, deciding whether this one applies.
|
|
119
|
-
*
|
|
120
|
-
* One condition, one key, one operator — `showIf: { key: 'source', op:
|
|
121
|
-
* 'equals', value: 'field' }` reads as the sentence a node would otherwise
|
|
122
|
-
* write under the field. The driving key MUST be a literal (it may not set
|
|
123
|
-
* `expressionAllowed`), because applicability has to be decidable while the
|
|
124
|
-
* author is typing; the same limit `dependsOn` carries, for the same reason.
|
|
125
|
-
*/
|
|
126
|
-
interface IShowIfCondition {
|
|
127
|
-
/** The `key` of another config field on the same node. */
|
|
128
|
-
key: string;
|
|
129
|
-
/** How to compare it — the shared comparison vocabulary, see `OPERATORS`. */
|
|
130
|
-
op: Operator;
|
|
131
|
-
/**
|
|
132
|
-
* What to compare it to. Left out by the four operators that read presence
|
|
133
|
-
* rather than content (`exists`, `notExists`, `isEmpty`, `isNotEmpty`).
|
|
134
|
-
*/
|
|
135
|
-
value?: string | number | boolean;
|
|
136
|
-
}
|
|
137
|
-
interface IConfigFieldBase {
|
|
138
|
-
key: string;
|
|
139
|
-
label: LocalizedString;
|
|
140
|
-
type: ConfigType;
|
|
141
|
-
description?: LocalizedString;
|
|
142
|
-
required?: boolean;
|
|
143
|
-
default?: unknown;
|
|
144
|
-
placeholder?: LocalizedString;
|
|
145
|
-
expressionAllowed?: boolean;
|
|
146
|
-
multiline?: boolean;
|
|
147
|
-
validation?: IConfigValidation;
|
|
148
|
-
options?: IConfigOption[];
|
|
149
|
-
/**
|
|
150
|
-
* When true, a *known* field's `options` are resolved at author time via the
|
|
151
|
-
* node's `loadOptions` (a live/dependent dropdown) instead of a static
|
|
152
|
-
* `options[]`. Applies to `select` / `multiselect`; left unset for ordinary
|
|
153
|
-
* static fields.
|
|
154
|
-
*
|
|
155
|
-
* Independent of `type: 'dynamic-schema'`: that field type triggers
|
|
156
|
-
* `resolveConfigSchema` on its own and does NOT require `dynamic: true`.
|
|
157
|
-
*/
|
|
158
|
-
dynamic?: boolean;
|
|
159
|
-
/**
|
|
160
|
-
* Config keys whose values drive this field's dynamic resolution. The editor
|
|
161
|
-
* re-resolves when one of them changes. A key listed here is
|
|
162
|
-
* *dependency-driving* and MUST be a literal (it may not set
|
|
163
|
-
* `expressionAllowed`), so its value is known at author time.
|
|
164
|
-
*/
|
|
165
|
-
dependsOn?: string[];
|
|
166
|
-
/**
|
|
167
|
-
* When this field applies at all. The editor draws it only while the
|
|
168
|
-
* condition holds, so a node says once in its manifest what it would
|
|
169
|
-
* otherwise have to explain in prose under every affected field.
|
|
170
|
-
*
|
|
171
|
-
* Not to be confused with `dependsOn`, which sits beside it and does a
|
|
172
|
-
* different thing: `dependsOn` re-*resolves* a dynamic field's options when
|
|
173
|
-
* another field changes, and never decides whether the field is drawn.
|
|
174
|
-
*
|
|
175
|
-
* Left unset, the field always applies — so this is additive: a node that
|
|
176
|
-
* says nothing behaves as it always did, and so does an editor that does not
|
|
177
|
-
* know the key.
|
|
178
|
-
*/
|
|
179
|
-
showIf?: IShowIfCondition;
|
|
180
|
-
/**
|
|
181
|
-
* What a value mapped into this field from an earlier step has to be — `'array'`
|
|
182
|
-
* for a setting that wants the list a previous node produced (PO-594).
|
|
183
|
-
*
|
|
184
|
-
* The editor uses it to put the earlier steps' outputs of that kind first in the
|
|
185
|
-
* `{ }` list, mark them, and offer the single matching one as a one-click answer
|
|
186
|
-
* under an empty field. So the author does not have to know what a previous node
|
|
187
|
-
* returns, or which of its fields is the list.
|
|
188
|
-
*
|
|
189
|
-
* Not to be confused with `type: 'array'`, which draws the setting itself as
|
|
190
|
-
* repeating rows the author fills in by hand. Only meaningful together with
|
|
191
|
-
* `expressionAllowed`. Left unset, every output is offered alike, as before.
|
|
192
|
-
*/
|
|
193
|
-
accepts?: DataType;
|
|
194
|
-
/**
|
|
195
|
-
* Only meaningful when `type === 'credentials-ref'`: the namespaced slug(s) of
|
|
196
|
-
* the credential type(s) this field accepts (e.g. `revenexx:smtp`). The editor
|
|
197
|
-
* lists tenant credential instances of these type(s); the blob stores the
|
|
198
|
-
* chosen instance UUID. Pass a single string when only one type is accepted,
|
|
199
|
-
* or an array to let the field accept instances of any of several types (e.g.
|
|
200
|
-
* a node that works with both an OAuth and an API-token credential).
|
|
201
|
-
*/
|
|
202
|
-
credentialType?: string | string[];
|
|
203
|
-
/**
|
|
204
|
-
* Only meaningful when `type === 'state-ref'`: which kind of state namespace
|
|
205
|
-
* this field accepts (PO-374). The editor lists the workflow's declared
|
|
206
|
-
* namespaces of that role — and lets the author declare a new one without
|
|
207
|
-
* leaving the node — and the blob stores the chosen NAME.
|
|
208
|
-
*
|
|
209
|
-
* Declare the field rather than hardcoding a namespace name in `execute`: a
|
|
210
|
-
* literal is a contract nothing checks, and the mismatch surfaces as a 403 in
|
|
211
|
-
* the first production run rather than as an empty picker while authoring. It
|
|
212
|
-
* also narrows what the node may reach — a namespace is reachable only from
|
|
213
|
-
* the node whose config names it.
|
|
214
|
-
*/
|
|
215
|
-
stateRole?: 'mapping' | 'cursor' | 'dedupe' | 'digest';
|
|
216
|
-
}
|
|
217
|
-
interface IConfigField extends IConfigFieldBase {
|
|
218
|
-
/**
|
|
219
|
-
* The sub-settings of a field that holds a group of them (`type: 'object'`).
|
|
220
|
-
*
|
|
221
|
-
* A full `IConfigField`, not the base. `IConfigFieldBase` already carries
|
|
222
|
-
* `options`, `default`, `required`, `description` and `showIf`, so those were
|
|
223
|
-
* never what a nested setting was missing — `properties` and `items` were: the
|
|
224
|
-
* base has neither, so a sub-setting could not itself group or repeat without
|
|
225
|
-
* a cast. The manifest schema permits that nesting and the engine's config
|
|
226
|
-
* walker reads nested fields as first-class; the type was the only place
|
|
227
|
-
* stopping at one level (PO-436).
|
|
228
|
-
*/
|
|
229
|
-
properties?: IConfigField[];
|
|
230
|
-
/**
|
|
231
|
-
* What one entry of a list field (`type: 'array'`) looks like.
|
|
232
|
-
*
|
|
233
|
-
* Full `IConfigField` for the same reason as `properties`, and this is the one
|
|
234
|
-
* that bit: `items.type: 'object'` plus `items.properties` is the repeating row
|
|
235
|
-
* every mapping-style setting is built from, and `items` typed as the base had
|
|
236
|
-
* no `properties` to give it. `SwitchNode` in integrations-nodes-core ends its
|
|
237
|
-
* items block with `as IConfigField` for exactly that reason.
|
|
238
|
-
*/
|
|
239
|
-
items?: IConfigField;
|
|
240
|
-
}
|
|
241
|
-
interface INodeDescription {
|
|
242
|
-
slug: string;
|
|
243
|
-
version: string;
|
|
244
|
-
category: NodeCategory;
|
|
245
|
-
name: LocalizedString;
|
|
246
|
-
description?: LocalizedString;
|
|
247
|
-
/** A Lucide icon name, `lucide:<kebab-name>` as listed on lucide.dev (e.g. `lucide:globe`). The studio draws it as named; a name it cannot draw becomes a generic box. */
|
|
248
|
-
icon?: string;
|
|
249
|
-
/**
|
|
250
|
-
* Curated node-picker group path, outermost first (max 4 levels), e.g.
|
|
251
|
-
* `[{ en: 'Business Central' }, { en: 'Sales Orders' }]`. Optional —
|
|
252
|
-
* pickers without it fall back to package/category grouping.
|
|
253
|
-
*/
|
|
254
|
-
groups?: LocalizedString[];
|
|
255
|
-
/**
|
|
256
|
-
* Where this node stands inside its innermost palette folder, among the nodes
|
|
257
|
-
* of the same package: lower comes first. Optional — the editor draws nodes
|
|
258
|
-
* with a position before those without, and those without in the order the
|
|
259
|
-
* package lists them in `NODES`, so a package that lists its main node first
|
|
260
|
-
* needs no position at all. Use it when the array order is not the palette
|
|
261
|
-
* order, e.g. when `NODES` is sorted for another reason. It orders nodes only
|
|
262
|
-
* within one package; where a folder takes nodes from several packages, the
|
|
263
|
-
* order between packages is the editor's.
|
|
264
|
-
*/
|
|
265
|
-
paletteOrder?: number;
|
|
266
|
-
/** Associated images (screenshots, logos, banners) shipped with the package. */
|
|
267
|
-
images?: IImage[];
|
|
268
|
-
inputs: Record<string, IInputPort>;
|
|
269
|
-
/**
|
|
270
|
-
* Output ports the workflow editor wires to downstream nodes. An empty array
|
|
271
|
-
* marks a **terminal node**: the path ends here and no edge may leave it —
|
|
272
|
-
* the mirror image of `inputs: {}` on a trigger. `StopAndErrorNode` is the
|
|
273
|
-
* canonical case; it always throws, so there is no success path to wire, and
|
|
274
|
-
* a port that can never fire would leave a dead handle on the canvas. The
|
|
275
|
-
* key stays required so terminality is declared, not forgotten.
|
|
276
|
-
*/
|
|
277
|
-
outputs: IOutputPort[];
|
|
278
|
-
config?: IConfigField[];
|
|
279
|
-
}
|
|
280
|
-
interface INodeContext {
|
|
281
|
-
signal: AbortSignal;
|
|
282
|
-
logger: {
|
|
283
|
-
info(message: string, meta?: Record<string, unknown>): void;
|
|
284
|
-
warn(message: string, meta?: Record<string, unknown>): void;
|
|
285
|
-
error(message: string, meta?: Record<string, unknown>): void;
|
|
286
|
-
};
|
|
287
|
-
secrets: {
|
|
288
|
-
get(key: string): Promise<string>;
|
|
289
|
-
};
|
|
290
|
-
/**
|
|
291
|
-
* Resolve the access data of a credential instance the node references via a
|
|
292
|
-
* `credentials-ref` config field. The runtime fulfils this from the
|
|
293
|
-
* credentials broker at execution time (inside the Temporal activity), so the
|
|
294
|
-
* returned value — e.g. a short-lived access token — is always current and
|
|
295
|
-
* never persisted into workflow history.
|
|
296
|
-
*/
|
|
297
|
-
credentials: {
|
|
298
|
-
get(credentialsId: string): Promise<Record<string, unknown>>;
|
|
299
|
-
};
|
|
300
|
-
/**
|
|
301
|
-
* The tenant state store: what this workflow already knows from earlier runs
|
|
302
|
-
* (PO-374). Every call names a namespace the workflow declared in its
|
|
303
|
-
* `state[]` block; anything else is refused by the engine, so a node cannot
|
|
304
|
-
* reach state its workflow did not ask for.
|
|
305
|
-
*/
|
|
306
|
-
state: INodeState;
|
|
307
|
-
}
|
|
308
|
-
/**
|
|
309
|
-
* The four things a workflow can remember between runs, and the reason each is
|
|
310
|
-
* its own operation rather than a generic get/set: the role decides *when* a
|
|
311
|
-
* write becomes visible, and that decision is the engine's to make, not the
|
|
312
|
-
* node author's.
|
|
313
|
-
*
|
|
314
|
-
* The four roles are named **mapping**, **cursor**, **dedupe** and **digest** —
|
|
315
|
-
* those are the values a `state-ref` field's `stateRole` takes. `claim` below is
|
|
316
|
-
* the *operation* on a dedupe namespace, not a role of its own.
|
|
317
|
-
*
|
|
318
|
-
* - **mapping** and **dedupe** take effect immediately. A correlation discarded
|
|
319
|
-
* because the run failed afterwards is how the next run creates a duplicate
|
|
320
|
-
* in the target system; a claim invisible to other runs protects against
|
|
321
|
-
* nothing.
|
|
322
|
-
* - **cursor** and **digest** are staged and adopted only when the run
|
|
323
|
-
* completes. A watermark that advances on a failed run leaves exactly the gap
|
|
324
|
-
* it exists to prevent, and a digest may only count once the write it
|
|
325
|
-
* describes actually went through.
|
|
326
|
-
*
|
|
327
|
-
* At author time — the editor's "test this node" button — the store is
|
|
328
|
-
* **read-only**: a rehearsal must not leave correlations behind that a later
|
|
329
|
-
* production run would treat as truth. The write calls reject there.
|
|
330
|
-
*/
|
|
331
|
-
interface INodeState {
|
|
332
|
-
/** Correlate an id on each side of an integration (PIM article ↔ ERP number). */
|
|
333
|
-
mapping: {
|
|
334
|
-
/**
|
|
335
|
-
* The partner id of a correlation, or `null` when it is unknown — which is
|
|
336
|
-
* the usual way to decide between creating and updating in the target
|
|
337
|
-
* system.
|
|
338
|
-
*
|
|
339
|
-
* `side` says which side of the pair `key` is on, not which side to return:
|
|
340
|
-
* the default `'left'` matches `key` against the left id and answers with
|
|
341
|
-
* the right one, and `'right'` does the reverse. It never searches both, so
|
|
342
|
-
* a lookup on the wrong side answers `null` rather than falling back — and
|
|
343
|
-
* `null` on the create path is what makes the target record a second time.
|
|
344
|
-
*/
|
|
345
|
-
get(namespace: string, key: string, side?: 'left' | 'right'): Promise<string | null>;
|
|
346
|
-
/**
|
|
347
|
-
* Record a correlation. Takes effect immediately, and re-pointing one side
|
|
348
|
-
* of an existing pair is refused: an id correlated two ways is always a bug,
|
|
349
|
-
* and no later sync can untangle it.
|
|
350
|
-
*/
|
|
351
|
-
put(namespace: string, left: string, right: string, metadata?: Record<string, unknown>): Promise<void>;
|
|
352
|
-
};
|
|
353
|
-
/** How far an incremental sync has read. */
|
|
354
|
-
cursor: {
|
|
355
|
-
/**
|
|
356
|
-
* The committed watermark — an arbitrary JSON value such as
|
|
357
|
-
* `{ updatedAfter }` or a provider page token — or `undefined` when the
|
|
358
|
-
* sync has never run. A value staged by *this* run is not visible yet.
|
|
359
|
-
*/
|
|
360
|
-
get(namespace: string, partitionKey?: string): Promise<unknown>;
|
|
361
|
-
/**
|
|
362
|
-
* Stage a watermark. Adopted only if the run completes, so a failed run
|
|
363
|
-
* leaves the previous value in place and the next run picks the gap up.
|
|
364
|
-
* `partitionKey` separates e.g. one shop or company code from another.
|
|
365
|
-
*/
|
|
366
|
-
set(namespace: string, value: unknown, partitionKey?: string): Promise<void>;
|
|
367
|
-
};
|
|
368
|
-
/**
|
|
369
|
-
* Claim a key for the duration of its TTL. `true` means this caller got it;
|
|
370
|
-
* `false` means the key is held and this delivery is a duplicate — the normal
|
|
371
|
-
* answer under at-least-once delivery, and a value to branch on rather than an
|
|
372
|
-
* error.
|
|
373
|
-
*
|
|
374
|
-
* **A claim outlives the attempt that made it.** The holder is not identified:
|
|
375
|
-
* a claim is taken over only once it has expired, so a retry of the very
|
|
376
|
-
* attempt that claimed the key — after a transient failure that happened
|
|
377
|
-
* *after* the claim — is also told `false`. Treating that as a duplicate drops
|
|
378
|
-
* the delivery for the rest of the TTL, which makes `ttlSeconds` the retry
|
|
379
|
-
* window as much as the duplicate-suppression window. Choose it against both,
|
|
380
|
-
* and do not claim earlier in the node than necessary.
|
|
381
|
-
*
|
|
382
|
-
* `ttlSeconds` defaults to 604800 (seven days) and is accepted between 1 and
|
|
383
|
-
* 31536000.
|
|
384
|
-
*/
|
|
385
|
-
claim(namespace: string, key: string, opts?: {
|
|
386
|
-
ttlSeconds?: number;
|
|
387
|
-
}): Promise<boolean>;
|
|
388
|
-
/** Skip expensive writes for entities that have not changed. */
|
|
389
|
-
digest: {
|
|
390
|
-
/** Whether the entity still carries this hash — i.e. the write can be skipped. */
|
|
391
|
-
unchanged(namespace: string, entityKey: string, digest: string): Promise<boolean>;
|
|
392
|
-
/** Stage a hash; adopted only if the run completes. Call it *after* the write. */
|
|
393
|
-
set(namespace: string, entityKey: string, digest: string): Promise<void>;
|
|
394
|
-
};
|
|
395
|
-
}
|
|
396
|
-
interface INodeResult {
|
|
397
|
-
outputs: Record<string, unknown>;
|
|
398
|
-
branch?: string;
|
|
399
|
-
}
|
|
400
|
-
/**
|
|
401
|
-
* Context handed to a node's *author-time* resolvers (`loadOptions`,
|
|
402
|
-
* `resolveConfigSchema`, `resolveOutputs`). These run in the node-runtime host
|
|
403
|
-
* (a Node side-container) while a user is editing the workflow — never in
|
|
404
|
-
* `execute`. The result is snapshotted into the workflow blob at save, so the
|
|
405
|
-
* runtime never calls these (PO-143).
|
|
406
|
-
*/
|
|
407
|
-
interface INodeAuthorContext {
|
|
408
|
-
signal: AbortSignal;
|
|
409
|
-
logger: {
|
|
410
|
-
info(message: string, meta?: Record<string, unknown>): void;
|
|
411
|
-
warn(message: string, meta?: Record<string, unknown>): void;
|
|
412
|
-
error(message: string, meta?: Record<string, unknown>): void;
|
|
413
|
-
};
|
|
414
|
-
/** The user's current partial config values. */
|
|
415
|
-
config: Record<string, unknown>;
|
|
416
|
-
/**
|
|
417
|
-
* Lazily resolve a `secret-ref` config field's value (a key in the tenant
|
|
418
|
-
* secret store → a single string). Mirrors {@link INodeContext.secrets} so a
|
|
419
|
-
* resolver that authenticates via a secret-ref uses the same call it would in
|
|
420
|
-
* `execute`. Resolves against the internal secret endpoint; only the keys the
|
|
421
|
-
* resolver actually asks for are fetched.
|
|
422
|
-
*/
|
|
423
|
-
secrets: {
|
|
424
|
-
get(key: string): Promise<string>;
|
|
425
|
-
};
|
|
426
|
-
/**
|
|
427
|
-
* Lazily resolve a `credentials-ref` instance's access data via the
|
|
428
|
-
* credentials broker (id → structured material, e.g. `{ accessToken }`).
|
|
429
|
-
* Mirrors {@link INodeContext.credentials}.
|
|
430
|
-
*/
|
|
431
|
-
credentials: {
|
|
432
|
-
get(credentialsId: string): Promise<Record<string, unknown>>;
|
|
433
|
-
};
|
|
434
|
-
/** Preferred locale for resolved labels, when the caller supplies one. */
|
|
435
|
-
locale?: string;
|
|
436
|
-
/**
|
|
437
|
-
* The operator's type-to-search term for the option list being resolved.
|
|
438
|
-
* Only set for `loadOptions` calls. Providers may pass it to a server-side
|
|
439
|
-
* search endpoint or use it to filter the options they return.
|
|
440
|
-
*/
|
|
441
|
-
search?: string;
|
|
442
|
-
}
|
|
443
|
-
interface INode {
|
|
444
|
-
description: INodeDescription;
|
|
445
|
-
execute(ctx: INodeContext, inputs: Record<string, unknown>): Promise<INodeResult>;
|
|
446
|
-
/**
|
|
447
|
-
* Author-time: resolve the options of a `dynamic` field (a live/dependent
|
|
448
|
-
* dropdown). `fieldKey` is the config key being resolved. Optional — declare
|
|
449
|
-
* it only for nodes with `dynamic` `select`/`multiselect` fields.
|
|
450
|
-
*/
|
|
451
|
-
loadOptions?(ctx: INodeAuthorContext, fieldKey: string): Promise<IConfigOption[]>;
|
|
452
|
-
/**
|
|
453
|
-
* Author-time: resolve the flat set of typed config fields that replaces a
|
|
454
|
-
* `type: 'dynamic-schema'` marker (e.g. an API's parameters once app + API
|
|
455
|
-
* are chosen). Returns fields in the same config-field grammar; the node is
|
|
456
|
-
* responsible for flattening any nested API shape into flat keys.
|
|
457
|
-
*/
|
|
458
|
-
resolveConfigSchema?(ctx: INodeAuthorContext): Promise<IConfigField[]>;
|
|
459
|
-
/**
|
|
460
|
-
* Author-time: resolve a generic output-port set for an output marked
|
|
461
|
-
* `resolveOutputs` (e.g. ports derived from a connected resource's schema).
|
|
462
|
-
*/
|
|
463
|
-
resolveOutputs?(ctx: INodeAuthorContext): Promise<IOutputPort[]>;
|
|
464
|
-
}
|
|
465
|
-
/**
|
|
466
|
-
* Optional capability interface for nodes that iterate over a collection.
|
|
467
|
-
* Implement this alongside {@link INode} to signal to the runtime that this
|
|
468
|
-
* node drives iteration — the runtime will call {@link extractItems} instead
|
|
469
|
-
* of relying on slug-based detection.
|
|
470
|
-
*
|
|
471
|
-
* This interface is the designated dispatch point for future child-workflow
|
|
472
|
-
* execution: once per-iteration isolation is needed, the runtime can swap the
|
|
473
|
-
* inline loop for `executeChild` calls without any changes to the node or SDK.
|
|
474
|
-
*/
|
|
475
|
-
interface INodeWithIteration {
|
|
476
|
-
/**
|
|
477
|
-
* Extracts the array of items to iterate over from the resolved inputs and
|
|
478
|
-
* config. Must be pure and synchronous — it runs inside a Temporal Activity.
|
|
479
|
-
*/
|
|
480
|
-
extractItems(inputs: Record<string, unknown>, config: Record<string, unknown>): unknown[];
|
|
481
|
-
}
|
|
482
|
-
/**
|
|
483
|
-
* Type guard that checks whether a node implements {@link INodeWithIteration}.
|
|
484
|
-
*/
|
|
485
|
-
declare function isNodeWithIteration(node: INode): node is INode & INodeWithIteration;
|
|
486
|
-
/**
|
|
487
|
-
* The authentication strategy a credential type uses. Determines which SDK
|
|
488
|
-
* base class a concrete credential extends and how the broker resolves it.
|
|
489
|
-
*/
|
|
490
|
-
type CredentialAuthKind = 'static' | 'api-key' | 'basic' | 'oauth2-client-credentials' | 'oauth2-authcode';
|
|
491
|
-
/**
|
|
492
|
-
* Field types for the connection parameters a credential type declares. A
|
|
493
|
-
* superset-subset of {@link ConfigType}: scalars plus `secret` for values that
|
|
494
|
-
* must be masked in the UI and never returned in plaintext by the public API.
|
|
495
|
-
*/
|
|
496
|
-
type CredentialFieldType = 'string' | 'number' | 'boolean' | 'select' | 'secret';
|
|
497
|
-
interface ICredentialField {
|
|
498
|
-
key: string;
|
|
499
|
-
label: LocalizedString;
|
|
500
|
-
type: CredentialFieldType;
|
|
501
|
-
description?: LocalizedString;
|
|
502
|
-
required?: boolean;
|
|
503
|
-
default?: unknown;
|
|
504
|
-
placeholder?: LocalizedString;
|
|
505
|
-
validation?: IConfigValidation;
|
|
506
|
-
options?: IConfigOption[];
|
|
507
|
-
}
|
|
508
|
-
/**
|
|
509
|
-
* The published contract of a credential type: how the editor renders its
|
|
510
|
-
* config form and which auth strategy the broker applies. The imperative
|
|
511
|
-
* `test`/`resolve` logic lives in the bundled {@link ICredential} code, not in
|
|
512
|
-
* this description.
|
|
513
|
-
*/
|
|
514
|
-
interface ICredentialDescription {
|
|
515
|
-
/** Stable namespaced identifier `<namespace>:<slug>` (e.g. `revenexx:smtp`). */
|
|
516
|
-
slug: string;
|
|
517
|
-
version: string;
|
|
518
|
-
name: LocalizedString;
|
|
519
|
-
description?: LocalizedString;
|
|
520
|
-
/** A Lucide icon name, `lucide:<kebab-name>` as listed on lucide.dev (e.g. `lucide:globe`). The studio draws it as named; a name it cannot draw becomes a generic box. */
|
|
521
|
-
icon?: string;
|
|
522
|
-
/** Associated images (screenshots, logos, banners) shipped with the package. */
|
|
523
|
-
images?: IImage[];
|
|
524
|
-
authKind: CredentialAuthKind;
|
|
525
|
-
fields: ICredentialField[];
|
|
526
|
-
}
|
|
527
|
-
/**
|
|
528
|
-
* Runtime context handed to a credential's `test`/`resolve`. Runs in the
|
|
529
|
-
* credentials broker (a Node side-container), never in workflow code.
|
|
530
|
-
*/
|
|
531
|
-
interface ICredentialContext {
|
|
532
|
-
signal: AbortSignal;
|
|
533
|
-
logger: {
|
|
534
|
-
info(message: string, meta?: Record<string, unknown>): void;
|
|
535
|
-
warn(message: string, meta?: Record<string, unknown>): void;
|
|
536
|
-
error(message: string, meta?: Record<string, unknown>): void;
|
|
537
|
-
};
|
|
538
|
-
/**
|
|
539
|
-
* Persist updated durable credentials (e.g. a rotated `refresh_token`) back
|
|
540
|
-
* to storage. The broker wires this to `PATCH /v1/internal/credentials/{id}/durable-creds`.
|
|
541
|
-
* Absent during pre-save tests where no instance exists yet.
|
|
542
|
-
*/
|
|
543
|
-
persistDurableCreds?(durableCreds: Record<string, unknown>): Promise<void>;
|
|
544
|
-
}
|
|
545
|
-
interface ICredentialTestResult {
|
|
546
|
-
ok: boolean;
|
|
547
|
-
message?: string;
|
|
548
|
-
}
|
|
549
|
-
interface ICredentialResolveResult {
|
|
550
|
-
/** The access data handed to the node (e.g. `{ host, port, user, password }` or `{ accessToken }`). */
|
|
551
|
-
credentials: Record<string, unknown>;
|
|
552
|
-
/** ISO-8601 expiry of the resolved access data; absent for non-expiring (static) credentials. */
|
|
553
|
-
expiresAt?: string;
|
|
554
|
-
}
|
|
555
|
-
/**
|
|
556
|
-
* A credential type implementation. `config` is the validated, user-entered
|
|
557
|
-
* field map; `durableCreds` holds system-managed long-lived secrets (e.g. a
|
|
558
|
-
* `refresh_token` obtained via 3-legged OAuth) and is `null` until they exist.
|
|
559
|
-
*/
|
|
560
|
-
interface ICredential {
|
|
561
|
-
description: ICredentialDescription;
|
|
562
|
-
test(ctx: ICredentialContext, config: Record<string, unknown>): Promise<ICredentialTestResult>;
|
|
563
|
-
resolve(ctx: ICredentialContext, config: Record<string, unknown>, durableCreds: Record<string, unknown> | null): Promise<ICredentialResolveResult>;
|
|
564
|
-
}
|
|
565
|
-
/**
|
|
566
|
-
* Optional capability for `oauth2-authcode` credentials that need the
|
|
567
|
-
* interactive (3-legged) consent dance. The broker exposes these via
|
|
568
|
-
* `/oauth/authorize-url` and `/oauth/exchange-code`; Laravel proxies them.
|
|
569
|
-
*/
|
|
570
|
-
interface ICredentialOAuthAuthorize {
|
|
571
|
-
buildAuthorizeUrl(ctx: ICredentialContext, config: Record<string, unknown>, params: {
|
|
572
|
-
redirectUri: string;
|
|
573
|
-
state: string;
|
|
574
|
-
}): Promise<{
|
|
575
|
-
authorizeUrl: string;
|
|
576
|
-
codeVerifier?: string;
|
|
577
|
-
}>;
|
|
578
|
-
exchangeCode(ctx: ICredentialContext, config: Record<string, unknown>, params: {
|
|
579
|
-
code: string;
|
|
580
|
-
redirectUri: string;
|
|
581
|
-
codeVerifier?: string;
|
|
582
|
-
}): Promise<{
|
|
583
|
-
durableCreds: Record<string, unknown>;
|
|
584
|
-
}>;
|
|
585
|
-
}
|
|
586
|
-
/**
|
|
587
|
-
* Type guard for credentials that implement the 3-legged OAuth consent flow.
|
|
588
|
-
*/
|
|
589
|
-
declare function isOAuthAuthorizeCredential(credential: ICredential): credential is ICredential & ICredentialOAuthAuthorize;
|
|
590
|
-
/** Difficulty level surfaced in the template gallery. */
|
|
591
|
-
type TemplateLevel = 'beginner' | 'intermediate' | 'advanced';
|
|
592
|
-
/** Trigger kind a template can ship; mirrors the server's `TriggerType`. */
|
|
593
|
-
type TemplateTriggerType = 'manual' | 'schedule' | 'webhook' | 'event';
|
|
594
|
-
/**
|
|
595
|
-
* A trigger a template instantiates alongside its workflow. The `handle` is a
|
|
596
|
-
* stable UUID the workflow blob's edges reference via `from.nodeId`. Per-type
|
|
597
|
-
* `config` shapes: `schedule` → `{ cron, timezone? }`, `webhook` →
|
|
598
|
-
* `{ method, secretRef?, public? }`, `event` → `{ subject }`, `manual` → none.
|
|
599
|
-
* The integrations server deep-validates `config` per `type` on publish.
|
|
600
|
-
*/
|
|
601
|
-
interface ITemplateTrigger {
|
|
602
|
-
/** Stable UUID the blob's edges reference via `from.nodeId`. */
|
|
603
|
-
handle: string;
|
|
604
|
-
type: TemplateTriggerType;
|
|
605
|
-
name?: string;
|
|
606
|
-
config?: Record<string, unknown>;
|
|
607
|
-
active?: boolean;
|
|
608
|
-
}
|
|
609
|
-
/**
|
|
610
|
-
* A workflow template a node package publishes: a ready-made workflow blueprint
|
|
611
|
-
* the editor offers as a starting point. Unlike {@link INode}/{@link ICredential}
|
|
612
|
-
* a template carries no executable code — it is plain data, so a package exports
|
|
613
|
-
* its `ITemplateDescription`s directly (no class wrapper).
|
|
614
|
-
*
|
|
615
|
-
* The `definition` is a workflow blob authored against the workflow-blob grammar
|
|
616
|
-
* named by `blobVersion`; the integrations server validates it on publish.
|
|
617
|
-
*/
|
|
618
|
-
interface ITemplateDescription {
|
|
619
|
-
/** Stable namespaced identifier `<namespace>:<slug>` (e.g. `revenexx:slack-to-crm`). */
|
|
620
|
-
slug: string;
|
|
621
|
-
version: string;
|
|
622
|
-
/** Free-form grouping label shown in the gallery (e.g. `sales`). */
|
|
623
|
-
category: string;
|
|
624
|
-
level: TemplateLevel;
|
|
625
|
-
name: LocalizedString;
|
|
626
|
-
/** One-line summary shown on the template card. */
|
|
627
|
-
shortDescription?: LocalizedString;
|
|
628
|
-
/** Long-form description in Markdown. */
|
|
629
|
-
description?: LocalizedString;
|
|
630
|
-
/** A Lucide icon name, `lucide:<kebab-name>` as listed on lucide.dev (e.g. `lucide:globe`). The studio draws it as named; a name it cannot draw becomes a generic box. */
|
|
631
|
-
icon?: string;
|
|
632
|
-
/** Free-form industry tags (e.g. `any`, `medical`). */
|
|
633
|
-
industries?: string[];
|
|
634
|
-
/** Free-form vendor tags (e.g. `pipedrive`, `slack`). */
|
|
635
|
-
vendors?: string[];
|
|
636
|
-
/** Associated images (screenshots, logos, banners) shipped with the package. */
|
|
637
|
-
images?: IImage[];
|
|
638
|
-
/** Workflow-blob schema version `definition` targets (e.g. `v0-draft`). */
|
|
639
|
-
blobVersion: string;
|
|
640
|
-
/** The workflow blob instantiated when a user picks this template. */
|
|
641
|
-
definition: Record<string, unknown>;
|
|
642
|
-
/**
|
|
643
|
-
* Triggers instantiated alongside the workflow (their `handle`s are
|
|
644
|
-
* referenced by `definition`'s edges). Omit for a manual-only template.
|
|
645
|
-
*/
|
|
646
|
-
triggers?: ITemplateTrigger[];
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
/**
|
|
650
|
-
* Reduce a {@link LocalizedString} to a single plain string.
|
|
651
|
-
*
|
|
652
|
-
* Every consumer (the UI, the Laravel side) repeats the same "is it a
|
|
653
|
-
* string or a localized map?" branch to render a name/label; this centralizes
|
|
654
|
-
* it so the rules are defined once:
|
|
655
|
-
*
|
|
656
|
-
* - a plain string is trimmed and returned (or `undefined` if blank);
|
|
657
|
-
* - a localized map prefers `fallbackLang` (default `en`), then the first
|
|
658
|
-
* non-empty value;
|
|
659
|
-
* - a missing, empty, or blank-only value yields `undefined`, so the caller
|
|
660
|
-
* can apply its own fallback.
|
|
661
|
-
*/
|
|
662
|
-
declare function normalizeLocalized(value: LocalizedString | undefined | null, fallbackLang?: string): string | undefined;
|
|
663
|
-
|
|
664
|
-
/**
|
|
665
|
-
* Whether a setting applies, given what the author has filled in so far.
|
|
666
|
-
*
|
|
667
|
-
* A setting with no condition always applies — which is what makes `showIf`
|
|
668
|
-
* additive: a node that says nothing gets the behaviour it has today, and an
|
|
669
|
-
* editor that does not know the key draws every field as before.
|
|
670
|
-
*
|
|
671
|
-
* The value the condition reads is taken from the config as it stands mid-edit,
|
|
672
|
-
* so it is routinely `undefined`: the moment a node is dropped on the canvas
|
|
673
|
-
* nothing is filled in. That is why `equals` against a missing value is `false`
|
|
674
|
-
* rather than an error — the setting does not apply *yet*.
|
|
675
|
-
*/
|
|
676
|
-
declare function settingApplies(field: {
|
|
677
|
-
showIf?: IShowIfCondition;
|
|
678
|
-
}, config: Record<string, unknown>): boolean;
|
|
679
|
-
|
|
680
|
-
/**
|
|
681
|
-
* Reduce an `IConfigField.credentialType` (a single slug or an array of slugs)
|
|
682
|
-
* to a normalized, deduplicated `string[]`.
|
|
683
|
-
*
|
|
684
|
-
* The union `string | string[]` exists so node authors can write the common
|
|
685
|
-
* single-type case as a bare string, but every consumer (the editor that
|
|
686
|
-
* lists tenant credential instances, the Laravel side) needs a list to
|
|
687
|
-
* iterate. This centralizes the
|
|
688
|
-
* "is it a string or an array?" branch — analogous to {@link normalizeLocalized}
|
|
689
|
-
* for `LocalizedString` — so the rules are defined once:
|
|
690
|
-
*
|
|
691
|
-
* - a single non-empty slug becomes a one-element array;
|
|
692
|
-
* - an array is trimmed of blank entries and deduplicated, preserving order;
|
|
693
|
-
* - `undefined`, an empty string, or an empty/all-blank array yields `[]`, so
|
|
694
|
-
* the caller can treat "accepts nothing" uniformly.
|
|
695
|
-
*/
|
|
696
|
-
declare function normalizeCredentialType(value: string | string[] | undefined): string[];
|
|
697
|
-
|
|
698
|
-
declare class NodeError extends Error {
|
|
699
|
-
readonly code: string;
|
|
700
|
-
readonly meta?: Record<string, unknown>;
|
|
701
|
-
constructor(code: string, message: string, meta?: Record<string, unknown>);
|
|
702
|
-
}
|
|
703
|
-
|
|
704
|
-
/**
|
|
705
|
-
* Canonical error-output shape emitted on a node's `error` port. It lives here
|
|
706
|
-
* rather than in a node package because all four of them were building it
|
|
707
|
-
* separately — `integrations-nodes-core` in an internal module, `deepl` in a
|
|
708
|
-
* copy of that module, Business Central in `bc-fetch.ts` and Pipedrive in
|
|
709
|
-
* `pipedrive-client.ts` — so the workflow editor's guarantee of one
|
|
710
|
-
* `{ code, message, status }` payload rested on four files agreeing by hand
|
|
711
|
-
* (PO-442; asked for by PO-134 in June and not done then).
|
|
712
|
-
*/
|
|
713
|
-
interface NodeErrorOutput {
|
|
714
|
-
/** Machine-readable error code, e.g. `HTTP_ERROR`, `TIMEOUT`, `REQUEST_FAILED`. */
|
|
715
|
-
code: string;
|
|
716
|
-
message: string;
|
|
717
|
-
/** HTTP status code, or `0` for timeouts / network / non-HTTP errors. */
|
|
718
|
-
status: number;
|
|
719
|
-
/** Optional response body — HTTP-specific. */
|
|
720
|
-
body?: unknown;
|
|
721
|
-
}
|
|
722
|
-
/**
|
|
723
|
-
* Normalize any caught value into the canonical {@link NodeErrorOutput} shape.
|
|
724
|
-
* Use this in a node's `catch` block before routing to the `error` port.
|
|
725
|
-
*/
|
|
726
|
-
declare function toErrorOutput(err: unknown): NodeErrorOutput;
|
|
727
|
-
/** Wrap an error output into an `INodeResult` routed to the `error` port. */
|
|
728
|
-
declare function errorResult(output: NodeErrorOutput, port?: string): INodeResult;
|
|
729
|
-
/**
|
|
730
|
-
* Catch-block shortcut: normalize any thrown value and route it to the failure
|
|
731
|
-
* output. The one helper every node's `catch` funnels into, so the
|
|
732
|
-
* `{ code, message, status }` mapping lives in a single place instead of being
|
|
733
|
-
* re-implemented per protocol (FTP/SFTP) or per package.
|
|
734
|
-
*
|
|
735
|
-
* `port` is the output taken, `error` unless the node declares its failure
|
|
736
|
-
* output under another name (`{ ...errorPort(), name: 'failed' }`) — pass the
|
|
737
|
-
* same name here, or the branch names a port that node does not have.
|
|
738
|
-
*/
|
|
739
|
-
declare function toErrorResult(err: unknown, port?: string): INodeResult;
|
|
740
|
-
/**
|
|
741
|
-
* Catch-block shortcut for the HTTP-style nodes whose failure output additionally
|
|
742
|
-
* declares `body`: the canonical `{ code, message, status }` mapping plus the
|
|
743
|
-
* response body a {@link NodeError} carries in `meta.body` (`null` when the
|
|
744
|
-
* failure never reached a response). `port` names the output as above.
|
|
745
|
-
*/
|
|
746
|
-
declare function httpErrorResult(err: unknown, port?: string): INodeResult;
|
|
747
|
-
/**
|
|
748
|
-
* Shared `error` output port — the uniform `{ code, message, status }` triple
|
|
749
|
-
* declared on every node that has an `error` branch. Pass `body: true` for
|
|
750
|
-
* HTTP-style nodes that additionally emit a response body on the port.
|
|
751
|
-
*
|
|
752
|
-
* The sentences travel with the port. The three values mean the same thing
|
|
753
|
-
* behind every node, so they are written here once instead of per node; a node
|
|
754
|
-
* whose failures are worth naming more precisely spreads the port and replaces
|
|
755
|
-
* only its `description` (`{ ...errorPort(), description: … }`).
|
|
756
|
-
*
|
|
757
|
-
* The second sentence of the description is the rule this port exists under,
|
|
758
|
-
* and it is promised in `specs/error-handling.md`: a fault in the author's own
|
|
759
|
-
* configuration ends the run, a fault met while running is offered here.
|
|
760
|
-
*/
|
|
761
|
-
declare function errorPort(opts?: {
|
|
762
|
-
body?: boolean;
|
|
763
|
-
}): IOutputPort;
|
|
764
|
-
|
|
765
|
-
declare function extractManifest(node: INode): INodeDescription;
|
|
766
|
-
declare function extractManifests(nodes: INode[]): INodeDescription[];
|
|
767
|
-
declare function extractCredentialManifest(credential: ICredential): ICredentialDescription;
|
|
768
|
-
declare function extractCredentialManifests(credentials: ICredential[]): ICredentialDescription[];
|
|
769
|
-
|
|
770
|
-
declare const DEFAULT_TIMEOUT_MS = 30000;
|
|
771
|
-
declare const MAX_TIMEOUT_MS = 120000;
|
|
772
|
-
/** Maximum number of redirect hops `safeFetch` follows before giving up. */
|
|
773
|
-
declare const MAX_REDIRECTS = 5;
|
|
774
|
-
/**
|
|
775
|
-
* Whether a request header with this name goes on to the next hop when
|
|
776
|
-
* {@link safeFetch} follows a redirect to another origin — `true` for `Accept`,
|
|
777
|
-
* `Accept-Encoding`, `Accept-Language`, `Content-Encoding`, `Content-Language`,
|
|
778
|
-
* `Content-Type`, `Idempotency-Key`, `If-Match`, `If-Modified-Since`,
|
|
779
|
-
* `If-None-Match`, `If-Range`, `If-Unmodified-Since`, `Range`, `SOAPAction` and
|
|
780
|
-
* `User-Agent`, in any case, and `false` for every other name. The keep-list is by
|
|
781
|
-
* name, so it can only keep a secret out of the headers it drops: a credential
|
|
782
|
-
* whose header name the author chooses must refuse a name this answers `true`
|
|
783
|
-
* for, or its key would cross with the harmless headers.
|
|
784
|
-
*/
|
|
785
|
-
declare function headerCrossesOrigin(name: string): boolean;
|
|
786
|
-
declare const DEFAULT_RETRY_ATTEMPTS = 0;
|
|
787
|
-
declare const MAX_RETRY_ATTEMPTS = 5;
|
|
788
|
-
declare const DEFAULT_RETRY_DELAY_MS = 1000;
|
|
789
|
-
/**
|
|
790
|
-
* Default cap for response bodies read via {@link readArrayBuffer} /
|
|
791
|
-
* {@link readText} / {@link readJsonOrText}. Guards the (shared) worker process
|
|
792
|
-
* against a single oversized response exhausting its memory.
|
|
793
|
-
*/
|
|
794
|
-
declare const DEFAULT_MAX_RESPONSE_BYTES: number;
|
|
795
|
-
/**
|
|
796
|
-
* Hard upper ceiling for any per-node `maxBytes`. Even a permissive node author
|
|
797
|
-
* (who calls {@link maxBytesConfigField} without an explicit `max`, or passes a
|
|
798
|
-
* large `maxBytes` to the `read*` helpers) can never lift the cap above this, so
|
|
799
|
-
* the shared worker's memory stays bounded. Analogous to {@link MAX_TIMEOUT_MS}.
|
|
800
|
-
*/
|
|
801
|
-
declare const MAX_RESPONSE_BYTES: number;
|
|
802
|
-
/** Clamp a requested `maxBytes` into `[1, MAX_RESPONSE_BYTES]`. */
|
|
803
|
-
declare function clampResponseBytes(maxBytes: number): number;
|
|
804
|
-
interface SafeFetchRetry {
|
|
805
|
-
attempts: number;
|
|
806
|
-
delayMs?: number;
|
|
807
|
-
}
|
|
808
|
-
interface SafeFetchOptions extends RequestInit {
|
|
809
|
-
timeoutMs?: number;
|
|
810
|
-
/** Pass `ctx.signal` so the workflow engine can cancel the request. */
|
|
811
|
-
signal?: AbortSignal;
|
|
812
|
-
retry?: SafeFetchRetry;
|
|
813
|
-
}
|
|
814
|
-
declare function safeFetch(url: string | URL, options?: SafeFetchOptions): Promise<Response>;
|
|
815
|
-
/**
|
|
816
|
-
* Read a response body into an ArrayBuffer while enforcing a hard byte cap.
|
|
817
|
-
*
|
|
818
|
-
* The `Content-Length` header is used as a fast-reject (bail before downloading
|
|
819
|
-
* anything), but the limit is *also* enforced while streaming, since the header
|
|
820
|
-
* can be absent or lie. On overrun the stream is cancelled and a
|
|
821
|
-
* `NodeError('RESPONSE_TOO_LARGE', …, { status })` is thrown. Both refusal paths
|
|
822
|
-
* cancel the body first, so neither leaves the connection held.
|
|
823
|
-
*/
|
|
824
|
-
declare function readArrayBuffer(res: Response, maxBytes?: number): Promise<ArrayBuffer>;
|
|
825
|
-
/** Read a response body as UTF-8 text, capped at `maxBytes` (see {@link readArrayBuffer}). */
|
|
826
|
-
declare function readText(res: Response, maxBytes?: number): Promise<string>;
|
|
827
|
-
/**
|
|
828
|
-
* Read a response body as JSON when the `Content-Type` denotes JSON (see
|
|
829
|
-
* {@link isJsonContentType}), otherwise as text — the content-type sniff
|
|
830
|
-
* previously duplicated across the HTTP/Upload/DeepL node sinks. Capped at
|
|
831
|
-
* `maxBytes` (see {@link readArrayBuffer}).
|
|
832
|
-
*
|
|
833
|
-
* A malformed JSON body surfaces as `NodeError('RESPONSE_PARSE_ERROR', …, { status })`
|
|
834
|
-
* rather than a raw `SyntaxError`, keeping to the SDK error contract.
|
|
835
|
-
*/
|
|
836
|
-
declare function readJsonOrText(res: Response, maxBytes?: number): Promise<unknown>;
|
|
837
|
-
/**
|
|
838
|
-
* The setting a node offers its workflow author for the response size cap,
|
|
839
|
-
* ready to spread into `description.config`.
|
|
840
|
-
*
|
|
841
|
-
* **What the node still has to do** (PO-497): declaring the setting does not
|
|
842
|
-
* wire it up. Read the configured value in `execute` and pass it on — the
|
|
843
|
-
* helpers here only produce the declaration, and a node that spreads this and
|
|
844
|
-
* never reads `config.maxBytes` offers a setting that does nothing.
|
|
845
|
-
*
|
|
846
|
-
* Everything an author reads is supplied in both languages the node packages
|
|
847
|
-
* write, so spreading this unchanged already satisfies the rule they hold.
|
|
848
|
-
* Nothing is left to the caller on that side: a node that wants different
|
|
849
|
-
* words replaces the field rather than completing it.
|
|
850
|
-
*/
|
|
851
|
-
declare function maxBytesConfigField(opts?: {
|
|
852
|
-
default?: number;
|
|
853
|
-
max?: number;
|
|
854
|
-
}): IConfigField;
|
|
855
|
-
/**
|
|
856
|
-
* The setting a node offers its workflow author for the request budget, ready
|
|
857
|
-
* to spread into `description.config`.
|
|
858
|
-
*
|
|
859
|
-
* **What the node still has to do** (PO-497): pass the configured value to
|
|
860
|
-
* `safeFetch` as `timeoutMs`. See {@link maxBytesConfigField} for the same note
|
|
861
|
-
* and for why the label and description are not the caller's to complete.
|
|
862
|
-
*/
|
|
863
|
-
declare function timeoutConfigField(opts?: {
|
|
864
|
-
default?: number;
|
|
865
|
-
max?: number;
|
|
866
|
-
}): IConfigField;
|
|
867
|
-
/**
|
|
868
|
-
* The two settings a node offers its workflow author for the retry budget,
|
|
869
|
-
* ready to spread into `description.config`.
|
|
870
|
-
*
|
|
871
|
-
* **What the node still has to do** (PO-497): pass both configured values to
|
|
872
|
-
* `safeFetch` as `retry.attempts` and `retry.delayMs`. See
|
|
873
|
-
* {@link maxBytesConfigField} for the same note and for why the labels and
|
|
874
|
-
* descriptions are not the caller's to complete.
|
|
875
|
-
*/
|
|
876
|
-
declare function retryConfigFields(opts?: {
|
|
877
|
-
defaultAttempts?: number;
|
|
878
|
-
defaultDelayMs?: number;
|
|
879
|
-
}): IConfigField[];
|
|
880
|
-
|
|
881
|
-
/**
|
|
882
|
-
* A resolved DNS address, mirroring the shape of Node's `dns.LookupAddress`.
|
|
883
|
-
* `family` is `4` or `6`; only `address` is consulted by the guard.
|
|
884
|
-
*/
|
|
885
|
-
interface LookupAddress {
|
|
886
|
-
address: string;
|
|
887
|
-
family: number;
|
|
888
|
-
}
|
|
889
|
-
/**
|
|
890
|
-
* Resolves a hostname to every address it maps to. Modelled on
|
|
891
|
-
* `dns.lookup(host, { all: true })`. Injectable so tests can drive the guard
|
|
892
|
-
* deterministically without real DNS — see {@link ssrfResolver}.
|
|
893
|
-
*/
|
|
894
|
-
type LookupFn = (hostname: string) => Promise<LookupAddress[]>;
|
|
895
|
-
/**
|
|
896
|
-
* Return `true` when `ip` (a literal IPv4/IPv6 address) points at a private,
|
|
897
|
-
* loopback, link-local, carrier-grade NAT, multicast, broadcast, reserved,
|
|
898
|
-
* unallocated or otherwise non-public target that a server-side fetch must never be
|
|
899
|
-
* steered to.
|
|
900
|
-
*
|
|
901
|
-
* The verdict is an allow-list: the address is refused unless the classification
|
|
902
|
-
* calls its range public unicast and — for IPv6, where that verdict is a default
|
|
903
|
-
* rather than a determination — it also sits inside the block IANA has allocated
|
|
904
|
-
* (see {@link GLOBAL_UNICAST_V6}). The IPv6 forms that embed an IPv4 address are
|
|
905
|
-
* unwrapped and the embedded address judged in its place, so a public host stays
|
|
906
|
-
* reachable when it is written that way. An address we cannot parse is treated as
|
|
907
|
-
* blocked (fail-closed).
|
|
908
|
-
*
|
|
909
|
-
* Promised behaviour: specs/ssrf-guard.md (AC-9, AC-11, AC-19).
|
|
910
|
-
*/
|
|
911
|
-
declare function isBlockedAddress(ip: string): boolean;
|
|
912
|
-
/**
|
|
913
|
-
* Assert that `url` is safe for a server-side fetch: an http(s) URL whose target
|
|
914
|
-
* resolves only to public addresses. Rejects non-http(s) protocols, empty hosts
|
|
915
|
-
* and `localhost`, checks literal-IP hosts directly, and otherwise resolves the
|
|
916
|
-
* hostname (via the injectable `lookup`, defaulting to {@link ssrfResolver}) and
|
|
917
|
-
* rejects if **any** resolved address is private/reserved. Throws
|
|
918
|
-
* `NodeError('BLOCKED_ADDRESS', …, { status: 0 })` on rejection.
|
|
919
|
-
*
|
|
920
|
-
* This is the pre-flight half of the guard: the connection that follows resolves
|
|
921
|
-
* the name again on its own, so `safeFetch` also judges the address the socket
|
|
922
|
-
* actually reached (PO-184). A caller that opens its own request after
|
|
923
|
-
* `assertPublicUrl` gets the check and not that second half.
|
|
924
|
-
*
|
|
925
|
-
* Pass `signal` (the per-request timeout/cancellation budget) so a hung or
|
|
926
|
-
* hostile DNS resolve cannot block the guard past that budget — see
|
|
927
|
-
* {@link resolveHost}.
|
|
928
|
-
*/
|
|
929
|
-
declare function assertPublicUrl(url: string | URL, opts?: {
|
|
930
|
-
lookup?: LookupFn;
|
|
931
|
-
signal?: AbortSignal;
|
|
932
|
-
}): Promise<void>;
|
|
933
|
-
|
|
934
|
-
/**
|
|
935
|
-
* Transport-agnostic retry/backoff primitive (PO-139).
|
|
936
|
-
*
|
|
937
|
-
* Wraps *any* async operation — raw `fetch`, an SDK client (axios), a future
|
|
938
|
-
* transport — with exponential backoff + full jitter, honouring a
|
|
939
|
-
* server-dictated delay (HTTP `Retry-After`) when the caller supplies one.
|
|
940
|
-
*
|
|
941
|
-
* The retry decision lives with the connector: it throws a {@link RetryableError}
|
|
942
|
-
* only when an attempt failed *and* is worth retrying. Everything else is
|
|
943
|
-
* rethrown as-is, and terminal API errors that connectors model as *values*
|
|
944
|
-
* (e.g. a `{ kind: 'http-error' }` union) simply flow back as the return value.
|
|
945
|
-
*
|
|
946
|
-
* This is deliberately NOT a circuit breaker, rate limiter, or HTTP client, and
|
|
947
|
-
* it does not replace Temporal activity-level retries — it is the fine-grained,
|
|
948
|
-
* idempotency-aware, `Retry-After`-honouring layer inside a single activity.
|
|
949
|
-
*/
|
|
950
|
-
/** Thrown by the wrapped fn to signal "this attempt failed but is retryable". */
|
|
951
|
-
declare class RetryableError extends Error {
|
|
952
|
-
/** Server-dictated delay before the next attempt, e.g. parsed from `Retry-After`. */
|
|
953
|
-
readonly retryAfterMs?: number;
|
|
954
|
-
constructor(message: string, opts?: {
|
|
955
|
-
retryAfterMs?: number;
|
|
956
|
-
cause?: unknown;
|
|
957
|
-
});
|
|
958
|
-
}
|
|
959
|
-
interface RetryPolicy {
|
|
960
|
-
/** Total tries including the first. */
|
|
961
|
-
maxAttempts: number;
|
|
962
|
-
/** Base delay for the exponential backoff. */
|
|
963
|
-
baseDelayMs: number;
|
|
964
|
-
/** Upper bound for any single computed delay. */
|
|
965
|
-
maxDelayMs: number;
|
|
966
|
-
/** Exponential growth factor. */
|
|
967
|
-
factor: number;
|
|
968
|
-
/** Apply full jitter to the computed backoff. */
|
|
969
|
-
jitter: boolean;
|
|
970
|
-
}
|
|
971
|
-
declare const DEFAULT_RETRY_POLICY: RetryPolicy;
|
|
972
|
-
interface RetryHooks {
|
|
973
|
-
/** Pass `ctx.signal` so cancelling a workflow cancels the wait (and stops retrying) immediately. */
|
|
974
|
-
signal: AbortSignal;
|
|
975
|
-
logger?: {
|
|
976
|
-
warn(msg: string, meta?: Record<string, unknown>): void;
|
|
977
|
-
};
|
|
978
|
-
onRetry?(info: {
|
|
979
|
-
attempt: number;
|
|
980
|
-
delayMs: number;
|
|
981
|
-
error: unknown;
|
|
982
|
-
}): void;
|
|
983
|
-
}
|
|
984
|
-
/**
|
|
985
|
-
* Pure backoff math, exported for testing/reuse. Returns the delay before the
|
|
986
|
-
* retry that follows the given (1-based) failed `attempt`: an exponential term
|
|
987
|
-
* `baseDelayMs * factor^(attempt-1)` capped at `maxDelayMs`, with full jitter
|
|
988
|
-
* (`random() * cap`) when `policy.jitter` is set.
|
|
989
|
-
*/
|
|
990
|
-
declare function backoffDelay(attempt: number, policy: RetryPolicy): number;
|
|
991
|
-
/**
|
|
992
|
-
* Abort-aware sleep. Resolves after `ms`, or rejects with the signal's reason
|
|
993
|
-
* (a DOMException `AbortError` by default) as soon as `signal` aborts — so a
|
|
994
|
-
* caller's retry loop stops cleanly instead of finishing the wait.
|
|
995
|
-
*/
|
|
996
|
-
declare function sleepWithSignal(ms: number, signal: AbortSignal): Promise<void>;
|
|
997
|
-
/**
|
|
998
|
-
* Runs `fn`; if it throws a {@link RetryableError} and attempts remain, waits
|
|
999
|
-
* (`RetryableError.retryAfterMs` when present, else the computed
|
|
1000
|
-
* {@link backoffDelay}) respecting `signal`, then retries. Any other throw is
|
|
1001
|
-
* rethrown immediately; on the final attempt the RetryableError itself is
|
|
1002
|
-
* thrown (preserving its `retryAfterMs`/`cause`), not its `.cause`.
|
|
1003
|
-
*
|
|
1004
|
-
* Distinct from `safeFetch`'s built-in fixed-delay `retry` option, which is
|
|
1005
|
-
* unchanged: that is a simple in-fetch retry; this is the richer, transport-
|
|
1006
|
-
* agnostic, `Retry-After`-aware primitive connectors wrap around any operation.
|
|
1007
|
-
*/
|
|
1008
|
-
declare function withRetry<T>(fn: (attempt: number) => Promise<T>, policy: Partial<RetryPolicy>, hooks: RetryHooks): Promise<T>;
|
|
1009
|
-
|
|
1010
|
-
/**
|
|
1011
|
-
* Envelope version expected by the integrations server-side
|
|
1012
|
-
* `TarballInspector`. Earlier (pre-registry) builds emitted a bare array
|
|
1013
|
-
* without any `manifestVersion` field; `v0-draft` is currently the only
|
|
1014
|
-
* accepted value (see `SchemaServiceProvider::DOMAIN_NODE` in the
|
|
1015
|
-
* integrations service). Keeping it versioned lets the registry evolve the
|
|
1016
|
-
* shape without breaking older publishers.
|
|
1017
|
-
*/
|
|
1018
|
-
declare const MANIFEST_VERSION = "v0-draft";
|
|
1019
|
-
interface NodeManifest {
|
|
1020
|
-
manifestVersion: typeof MANIFEST_VERSION;
|
|
1021
|
-
nodes: INodeDescription[];
|
|
1022
|
-
/**
|
|
1023
|
-
* Credential types this package publishes. Optional and additive: packages
|
|
1024
|
-
* that ship no credentials omit it (older publishers never set it).
|
|
1025
|
-
*/
|
|
1026
|
-
credentials?: ICredentialDescription[];
|
|
1027
|
-
/**
|
|
1028
|
-
* Workflow templates this package publishes. Optional and additive: packages
|
|
1029
|
-
* that ship no templates omit it. Templates are plain data (no executable
|
|
1030
|
-
* code), so they are carried verbatim from the package's `TEMPLATES` export.
|
|
1031
|
-
*/
|
|
1032
|
-
templates?: ITemplateDescription[];
|
|
1033
|
-
}
|
|
1034
|
-
/**
|
|
1035
|
-
* The registry-relevant fields of a node package's `package.json`, as read by
|
|
1036
|
-
* the CLI. `name`/`version` identify the package; `displayName` is the
|
|
1037
|
-
* human-readable bundle label shown in the editor's node palette (e.g.
|
|
1038
|
-
* „Business Central"); `icon` marks its folder there. All four are read straight
|
|
1039
|
-
* from `package.json` by the integrations server on upload — the CLI reads them
|
|
1040
|
-
* only to warn about a missing label, to annotate the manifest log line, and to
|
|
1041
|
-
* stop on an icon the server would refuse.
|
|
1042
|
-
*
|
|
1043
|
-
* The label lives under a namespaced `revenexx` group in `package.json`
|
|
1044
|
-
* (`{ "revenexx": { "displayName": "…" } }`), not a bespoke top-level key, so
|
|
1045
|
-
* it can't collide with unrelated tooling that squats on `displayName`.
|
|
1046
|
-
*/
|
|
1047
|
-
interface NodePackageMeta {
|
|
1048
|
-
name: string;
|
|
1049
|
-
version: string;
|
|
1050
|
-
/** Human-readable bundle label (e.g. „Business Central"); optional. */
|
|
1051
|
-
displayName?: string;
|
|
1052
|
-
/**
|
|
1053
|
-
* The glyph that marks the package's folder in the studio's palette, as
|
|
1054
|
-
* `lucide:<kebab-name>` (e.g. `lucide:bell`); optional. Declared by a package
|
|
1055
|
-
* holding several services, which has no one vendor logo to mark its folder
|
|
1056
|
-
* with — a package that is one vendor leaves it out. Trimmed, and a value that
|
|
1057
|
-
* is not a string counts as none, as the server reads it; whether it is well
|
|
1058
|
-
* formed is {@link packageIconProblem}'s question.
|
|
1059
|
-
*/
|
|
1060
|
-
icon?: string;
|
|
1061
|
-
}
|
|
1062
|
-
/**
|
|
1063
|
-
* Why a declared `revenexx.icon` would be refused by the registry, or
|
|
1064
|
-
* `undefined` when it is fine (or absent). Lets the build stop where the author
|
|
1065
|
-
* can still fix it, rather than at upload. Takes the icon as
|
|
1066
|
-
* {@link parsePackageMeta} returns it — trimmed as the registry trims it; an
|
|
1067
|
-
* untrimmed raw value can be refused here although the registry accepts it.
|
|
1068
|
-
*/
|
|
1069
|
-
declare function packageIconProblem(icon: string | undefined): string | undefined;
|
|
1070
|
-
/**
|
|
1071
|
-
* Extracts {@link NodePackageMeta} from parsed `package.json` contents, keeping
|
|
1072
|
-
* only the registry-relevant fields and coercing anything malformed to a safe
|
|
1073
|
-
* shape. All four fields are trimmed — the icon of ASCII whitespace only, as the
|
|
1074
|
-
* server's PHP `trim()` does, so a pasted no-break space is refused here as it
|
|
1075
|
-
* is there rather than trimmed away; a blank or whitespace-only value becomes
|
|
1076
|
-
* `''` (`name`/`version`) or `undefined` (`displayName`, `icon`, matching how the
|
|
1077
|
-
* server treats them), so whitespace can't masquerade as a present value in
|
|
1078
|
-
* tooling. The bundle label and the folder icon are read from the `revenexx`
|
|
1079
|
-
* group (`revenexx.displayName`, `revenexx.icon`).
|
|
1080
|
-
* Does not validate that `name`/`version` are present — the integrations server
|
|
1081
|
-
* enforces that on upload; this is a typed, lenient read for tooling.
|
|
1082
|
-
*/
|
|
1083
|
-
declare function parsePackageMeta(raw: unknown): NodePackageMeta;
|
|
1084
|
-
/**
|
|
1085
|
-
* Builds the manifest envelope from a package's `NODES` (and optional
|
|
1086
|
-
* `CREDENTIALS` / `TEMPLATES`) exports. The result is what gets written to
|
|
1087
|
-
* `dist/manifest.json` and uploaded to the registry inside the tarball. The
|
|
1088
|
-
* bundle label is NOT carried here — the integrations server reads it straight
|
|
1089
|
-
* from `package.json` (`revenexx.displayName`).
|
|
1090
|
-
*/
|
|
1091
|
-
declare function buildManifest(nodes: INode[], credentials?: ICredential[], templates?: ITemplateDescription[]): NodeManifest;
|
|
1092
|
-
|
|
1093
|
-
/**
|
|
1094
|
-
* Reusable credential base classes. Concrete credential types (in node
|
|
1095
|
-
* packages) extend one of these and only supply field mappings / endpoints —
|
|
1096
|
-
* the auth strategy itself lives here so it is implemented once.
|
|
1097
|
-
*
|
|
1098
|
-
* All of this runs in the credentials broker (a Node side-container), never in
|
|
1099
|
-
* workflow code, so wall-clock time and network I/O are fine.
|
|
1100
|
-
*/
|
|
1101
|
-
type Config = Record<string, unknown>;
|
|
1102
|
-
type DurableCreds = Record<string, unknown> | null;
|
|
1103
|
-
interface OAuthTokenResponse {
|
|
1104
|
-
access_token?: string;
|
|
1105
|
-
refresh_token?: string;
|
|
1106
|
-
expires_in?: number;
|
|
1107
|
-
token_type?: string;
|
|
1108
|
-
/**
|
|
1109
|
-
* Provider-specific base URL the access token must target (e.g. Pipedrive
|
|
1110
|
-
* returns the company-scoped `api_domain` on both the initial exchange and
|
|
1111
|
-
* every refresh). Absent for providers that use a single global API host.
|
|
1112
|
-
*/
|
|
1113
|
-
api_domain?: string;
|
|
1114
|
-
[key: string]: unknown;
|
|
1115
|
-
}
|
|
1116
|
-
/**
|
|
1117
|
-
* The base for every credential type. Stores the published description and
|
|
1118
|
-
* provides a small HTTP helper. `resolve` is abstract; `test` defaults to a
|
|
1119
|
-
* presence check and should be overridden where a real connection test exists.
|
|
1120
|
-
*/
|
|
1121
|
-
declare abstract class BaseCredential implements ICredential {
|
|
1122
|
-
abstract readonly description: ICredentialDescription;
|
|
1123
|
-
abstract resolve(ctx: ICredentialContext, config: Config, durableCreds: DurableCreds): Promise<ICredentialResolveResult>;
|
|
1124
|
-
test(_ctx: ICredentialContext, config: Config): Promise<ICredentialTestResult>;
|
|
1125
|
-
/**
|
|
1126
|
-
* POST `application/x-www-form-urlencoded` and parse a JSON token response.
|
|
1127
|
-
*
|
|
1128
|
-
* Goes through {@link safeFetch}, not a raw `fetch` (PO-185). The token
|
|
1129
|
-
* endpoint is not a constant: `tokenUrl(config)` derives it from credential
|
|
1130
|
-
* config, i.e. from a value someone types into a credential form. A raw fetch
|
|
1131
|
-
* would happily POST the client secret at `http://169.254.169.254/…` or any
|
|
1132
|
-
* other internal address, which is precisely what `assertPublicUrl` exists to
|
|
1133
|
-
* prevent. Every OAuth token exchange in this file — client-credentials,
|
|
1134
|
-
* auth-code, refresh — funnels through here, so this is the one call site that
|
|
1135
|
-
* has to hold.
|
|
1136
|
-
*
|
|
1137
|
-
* Two behaviours safeFetch adds, both wanted here: a request budget
|
|
1138
|
-
* (`DEFAULT_TIMEOUT_MS`) on top of `ctx.signal`, and manual redirect handling
|
|
1139
|
-
* that re-checks each hop and drops `Authorization` across an origin boundary.
|
|
1140
|
-
* Note that a 301/302 answer to this POST is downgraded to a bodiless GET —
|
|
1141
|
-
* per the redirect rules, not a quirk of ours. Real token endpoints do not
|
|
1142
|
-
* redirect; one that does was never going to complete the exchange anyway.
|
|
1143
|
-
*
|
|
1144
|
-
* The third protection is the caller's own: safeFetch hands back a `Response`
|
|
1145
|
-
* and reading it is not its job, so the answer goes through {@link readText}
|
|
1146
|
-
* under {@link MAX_TOKEN_RESPONSE_BYTES} rather than a bare `res.text()`.
|
|
1147
|
-
* Both branches below read that same string, so the cap covers the refusal as
|
|
1148
|
-
* well as the token — an error body is the less predictable of the two.
|
|
1149
|
-
*/
|
|
1150
|
-
protected postForm(ctx: ICredentialContext, url: string, form: Record<string, string | undefined>, headers?: Record<string, string>): Promise<OAuthTokenResponse>;
|
|
1151
|
-
}
|
|
1152
|
-
/**
|
|
1153
|
-
* "Simple value retrieval": the validated config fields ARE the access data
|
|
1154
|
-
* handed to the node (e.g. SMTP host/port/user/password, SFTP host/key). No
|
|
1155
|
-
* token, no expiry. Override `test` to add a real connection check.
|
|
1156
|
-
*/
|
|
1157
|
-
declare abstract class SimpleValueCredential extends BaseCredential {
|
|
1158
|
-
resolve(_ctx: ICredentialContext, config: Config, _durableCreds: DurableCreds): Promise<ICredentialResolveResult>;
|
|
1159
|
-
}
|
|
1160
|
-
/**
|
|
1161
|
-
* API-key credential. Reads the key from the configured field and hands it to
|
|
1162
|
-
* the node under that same name; override {@link credentialShape} where the
|
|
1163
|
-
* node needs something else, or more than the key alone.
|
|
1164
|
-
*/
|
|
1165
|
-
declare abstract class ApiKeyCredential extends BaseCredential {
|
|
1166
|
-
/**
|
|
1167
|
-
* Which field of the credential form the key is typed into. Override it to
|
|
1168
|
-
* name the form field this provider calls its key — `botToken`, `authKey`,
|
|
1169
|
-
* `webhookUrl`.
|
|
1170
|
-
*
|
|
1171
|
-
* **What this does not do** (PO-497): it does not decide what a node looks
|
|
1172
|
-
* the key up under at run time. It used to do only half of that — it changed
|
|
1173
|
-
* which field was *read* while {@link credentialShape} went on emitting
|
|
1174
|
-
* `apiKey` — so a credential that renamed its form field alone handed the
|
|
1175
|
-
* node a key under a name it was never told to expect, and the node refused
|
|
1176
|
-
* a perfectly good key as a missing one. The default shape now follows this
|
|
1177
|
-
* name, so overriding this alone is correct on both sides. Overriding
|
|
1178
|
-
* {@link credentialShape} as well is still how a credential emits a
|
|
1179
|
-
* different name, or more than the key.
|
|
1180
|
-
*
|
|
1181
|
-
* Note that no node test can see a mismatch between the two: a mocked node
|
|
1182
|
-
* context is seeded with the resolved blob directly and never calls
|
|
1183
|
-
* `resolve`. Only a credential test asserting the whole resolved shape
|
|
1184
|
-
* catches it, and asserting it loosely still misses — a partial match is
|
|
1185
|
-
* happy with the wrong key present beside the right one.
|
|
1186
|
-
*/
|
|
1187
|
-
protected apiKeyField(): string;
|
|
1188
|
-
/**
|
|
1189
|
-
* What the node receives. Defaults to the key under the name
|
|
1190
|
-
* {@link apiKeyField} returns, so the two cannot drift apart by default.
|
|
1191
|
-
*
|
|
1192
|
-
* Override it where the node needs a different name than the form uses, or
|
|
1193
|
-
* needs more than the key — a base URL derived from it, say.
|
|
1194
|
-
*/
|
|
1195
|
-
protected credentialShape(apiKey: string): Record<string, unknown>;
|
|
1196
|
-
resolve(_ctx: ICredentialContext, config: Config, _durableCreds: DurableCreds): Promise<ICredentialResolveResult>;
|
|
1197
|
-
}
|
|
1198
|
-
/**
|
|
1199
|
-
* HTTP Basic auth credential. Exposes `{ username, password }` by default.
|
|
1200
|
-
*/
|
|
1201
|
-
declare abstract class BasicAuthCredential extends BaseCredential {
|
|
1202
|
-
protected usernameField(): string;
|
|
1203
|
-
protected passwordField(): string;
|
|
1204
|
-
resolve(_ctx: ICredentialContext, config: Config, _durableCreds: DurableCreds): Promise<ICredentialResolveResult>;
|
|
1205
|
-
}
|
|
1206
|
-
/**
|
|
1207
|
-
* OAuth2 client-credentials grant (service-to-service, no user, no
|
|
1208
|
-
* refresh_token). Concrete types supply the token endpoint + client creds via
|
|
1209
|
-
* the config field map. Robust to downtime: a fresh access token is minted on
|
|
1210
|
-
* every resolve when the broker cache misses.
|
|
1211
|
-
*/
|
|
1212
|
-
declare abstract class OAuth2ClientCredentialsCredential extends BaseCredential {
|
|
1213
|
-
protected abstract tokenUrl(config: Config): string;
|
|
1214
|
-
protected abstract clientId(config: Config): string;
|
|
1215
|
-
protected abstract clientSecret(config: Config): string;
|
|
1216
|
-
protected scope(_config: Config): string | undefined;
|
|
1217
|
-
/** Map the raw token response to the node-facing credentials. */
|
|
1218
|
-
protected credentialShape(token: OAuthTokenResponse): Record<string, unknown>;
|
|
1219
|
-
resolve(ctx: ICredentialContext, config: Config, _durableCreds: DurableCreds): Promise<ICredentialResolveResult>;
|
|
1220
|
-
test(ctx: ICredentialContext, config: Config): Promise<ICredentialTestResult>;
|
|
1221
|
-
}
|
|
1222
|
-
/**
|
|
1223
|
-
* OAuth2 authorization-code grant (3-legged, interactive consent). Implements
|
|
1224
|
-
* the consent dance (with PKCE by default), code exchange, and refresh-token
|
|
1225
|
-
* based resolution. The long-lived `refresh_token` is stored in durableCreds;
|
|
1226
|
-
* if the provider rotates it, the new one is persisted via the context.
|
|
1227
|
-
*/
|
|
1228
|
-
declare abstract class OAuth2AuthCodeCredential extends BaseCredential implements ICredentialOAuthAuthorize {
|
|
1229
|
-
protected abstract authorizeUrl(config: Config): string;
|
|
1230
|
-
protected abstract tokenUrl(config: Config): string;
|
|
1231
|
-
protected abstract clientId(config: Config): string;
|
|
1232
|
-
protected abstract clientSecret(config: Config): string;
|
|
1233
|
-
protected scope(_config: Config): string | undefined;
|
|
1234
|
-
protected usePkce(): boolean;
|
|
1235
|
-
protected credentialShape(token: OAuthTokenResponse): Record<string, unknown>;
|
|
1236
|
-
buildAuthorizeUrl(_ctx: ICredentialContext, config: Config, params: {
|
|
1237
|
-
redirectUri: string;
|
|
1238
|
-
state: string;
|
|
1239
|
-
}): Promise<{
|
|
1240
|
-
authorizeUrl: string;
|
|
1241
|
-
codeVerifier?: string;
|
|
1242
|
-
}>;
|
|
1243
|
-
exchangeCode(ctx: ICredentialContext, config: Config, params: {
|
|
1244
|
-
code: string;
|
|
1245
|
-
redirectUri: string;
|
|
1246
|
-
codeVerifier?: string;
|
|
1247
|
-
}): Promise<{
|
|
1248
|
-
durableCreds: Record<string, unknown>;
|
|
1249
|
-
}>;
|
|
1250
|
-
resolve(ctx: ICredentialContext, config: Config, durableCreds: DurableCreds): Promise<ICredentialResolveResult>;
|
|
1251
|
-
test(_ctx: ICredentialContext, config: Config): Promise<ICredentialTestResult>;
|
|
1252
|
-
}
|
|
1253
|
-
|
|
1254
|
-
export { ApiKeyCredential, BaseCredential, BasicAuthCredential, type ConfigType, type CredentialAuthKind, type CredentialFieldType, DEFAULT_MAX_RESPONSE_BYTES, DEFAULT_RETRY_ATTEMPTS, DEFAULT_RETRY_DELAY_MS, DEFAULT_RETRY_POLICY, DEFAULT_TIMEOUT_MS, type DataType, type IConfigField, type IConfigFieldBase, type IConfigOption, type IConfigValidation, type ICredential, type ICredentialContext, type ICredentialDescription, type ICredentialField, type ICredentialOAuthAuthorize, type ICredentialResolveResult, type ICredentialTestResult, type IImage, type IInputPort, type INode, type INodeAuthorContext, type INodeContext, type INodeDescription, type INodeResult, type INodeState, type INodeWithIteration, type IOutputField, type IOutputPort, type IShowIfCondition, type ITemplateDescription, type ITemplateTrigger, type ImageCategory, type LocalizedString, type LookupAddress, type LookupFn, MANIFEST_VERSION, MAX_REDIRECTS, MAX_RESPONSE_BYTES, MAX_RETRY_ATTEMPTS, MAX_TIMEOUT_MS, type NodeCategory, NodeError, type NodeErrorOutput, type NodeManifest, type NodePackageMeta, OAuth2AuthCodeCredential, OAuth2ClientCredentialsCredential, type OAuthTokenResponse, OPERATORS, type Operator, type OutputKind, type RetryHooks, type RetryPolicy, RetryableError, type SafeFetchOptions, type SafeFetchRetry, SimpleValueCredential, type TemplateLevel, type TemplateTriggerType, VALUELESS_OPERATORS, assertPublicUrl, backoffDelay, buildManifest, clampResponseBytes, errorPort, errorResult, evaluate, extractCredentialManifest, extractCredentialManifests, extractManifest, extractManifests, headerCrossesOrigin, httpErrorResult, isBlockedAddress, isNodeWithIteration, isOAuthAuthorizeCredential, isOperator, maxBytesConfigField, normalizeCredentialType, normalizeLocalized, packageIconProblem, parsePackageMeta, readArrayBuffer, readJsonOrText, readText, retryConfigFields, safeFetch, settingApplies, sleepWithSignal, takesValue, timeoutConfigField, toErrorOutput, toErrorResult, withRetry };
|