@revenexx/integrations-node-sdk 1.9.0 → 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.d.ts +14 -1254
- 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/types.d.ts
ADDED
|
@@ -0,0 +1,620 @@
|
|
|
1
|
+
import type { Operator } from './operators.js';
|
|
2
|
+
export type LocalizedString = string | Record<string, string>;
|
|
3
|
+
export type DataType = 'any' | 'object' | 'array' | 'string' | 'number' | 'boolean';
|
|
4
|
+
export type OutputKind = 'default' | 'branch' | 'error';
|
|
5
|
+
export type NodeCategory = 'trigger' | 'action' | 'transform' | 'control' | 'io';
|
|
6
|
+
export type ConfigType = 'string' | 'number' | 'boolean' | 'select' | 'multiselect' | 'object' | 'array' | 'expression' | 'secret-ref' | 'credentials-ref' | 'state-ref' | 'dynamic-schema';
|
|
7
|
+
/**
|
|
8
|
+
* Semantic category of an image, driving how the registry/CDN organises and
|
|
9
|
+
* displays it.
|
|
10
|
+
*/
|
|
11
|
+
export type ImageCategory = 'screenshot' | 'logo' | 'banner' | 'icon' | 'other';
|
|
12
|
+
/**
|
|
13
|
+
* A single image a node, credential type, or template ships alongside its
|
|
14
|
+
* description (e.g. a screenshot, logo, or banner). The file is bundled into
|
|
15
|
+
* the package tarball and uploaded to the Revenexx CDN when the package is
|
|
16
|
+
* published.
|
|
17
|
+
*/
|
|
18
|
+
export interface IImage {
|
|
19
|
+
/** Path to the image file, relative to the package root (e.g. `images/screenshot.png`). */
|
|
20
|
+
src: string;
|
|
21
|
+
alt: LocalizedString;
|
|
22
|
+
title?: LocalizedString;
|
|
23
|
+
category: ImageCategory;
|
|
24
|
+
}
|
|
25
|
+
export interface IInputPort {
|
|
26
|
+
dataType: DataType;
|
|
27
|
+
required?: boolean;
|
|
28
|
+
description?: LocalizedString;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* One value an output carries, as the editor offers it to the next step.
|
|
32
|
+
*
|
|
33
|
+
* Deliberately flat: there is no member describing what is inside the value,
|
|
34
|
+
* because the registry's manifest schema refuses any key beside these three and
|
|
35
|
+
* the studio reads none. What is inside a value is declared beside it, under a
|
|
36
|
+
* dotted name — see `IOutputPort.fields` (PO-408).
|
|
37
|
+
*/
|
|
38
|
+
export interface IOutputField {
|
|
39
|
+
dataType: DataType;
|
|
40
|
+
/**
|
|
41
|
+
* What the value is called, for an author picking it in the editor's `{ }` list —
|
|
42
|
+
* "Rechnungen" where the key is `invoices` (PO-594). Left out, the editor shows the
|
|
43
|
+
* key, as before.
|
|
44
|
+
*/
|
|
45
|
+
label?: LocalizedString;
|
|
46
|
+
description?: LocalizedString;
|
|
47
|
+
}
|
|
48
|
+
export interface IOutputPort {
|
|
49
|
+
kind: OutputKind;
|
|
50
|
+
dataType: DataType;
|
|
51
|
+
name?: string;
|
|
52
|
+
label?: LocalizedString;
|
|
53
|
+
description?: LocalizedString;
|
|
54
|
+
/**
|
|
55
|
+
* The values this output carries, by name.
|
|
56
|
+
*
|
|
57
|
+
* A name containing `.` is a path into the value: `counts` as `object` and
|
|
58
|
+
* `counts.added` as `number`, side by side, offers both the object and the
|
|
59
|
+
* number inside it. The studio and the worker resolve that name as a path, so
|
|
60
|
+
* the record the node emits must really be nested — a record carrying a
|
|
61
|
+
* literal `"counts.added"` key is not reached by it (specs/output-fields.md).
|
|
62
|
+
*/
|
|
63
|
+
fields?: Record<string, IOutputField>;
|
|
64
|
+
sourceFromConfig?: string;
|
|
65
|
+
/**
|
|
66
|
+
* Marks a generic port set resolved at author time by the node's
|
|
67
|
+
* `resolveOutputs` callback (PO-143), rather than a static `name` or a
|
|
68
|
+
* config-driven `sourceFromConfig`. Set at most one of `name`,
|
|
69
|
+
* `sourceFromConfig`, `resolveOutputs` — this is a server-validated
|
|
70
|
+
* constraint on publish, not enforced by this (intentionally flat) type.
|
|
71
|
+
*/
|
|
72
|
+
resolveOutputs?: boolean;
|
|
73
|
+
fallback?: {
|
|
74
|
+
name: string;
|
|
75
|
+
label?: LocalizedString;
|
|
76
|
+
description?: LocalizedString;
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
export interface IConfigOption {
|
|
80
|
+
value: string | number | boolean;
|
|
81
|
+
label: LocalizedString;
|
|
82
|
+
}
|
|
83
|
+
export interface IConfigValidation {
|
|
84
|
+
pattern?: string;
|
|
85
|
+
min?: number;
|
|
86
|
+
max?: number;
|
|
87
|
+
minLength?: number;
|
|
88
|
+
maxLength?: number;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* A condition on another setting's value, deciding whether this one applies.
|
|
92
|
+
*
|
|
93
|
+
* One condition, one key, one operator — `showIf: { key: 'source', op:
|
|
94
|
+
* 'equals', value: 'field' }` reads as the sentence a node would otherwise
|
|
95
|
+
* write under the field. The driving key MUST be a literal (it may not set
|
|
96
|
+
* `expressionAllowed`), because applicability has to be decidable while the
|
|
97
|
+
* author is typing; the same limit `dependsOn` carries, for the same reason.
|
|
98
|
+
*/
|
|
99
|
+
export interface IShowIfCondition {
|
|
100
|
+
/** The `key` of another config field on the same node. */
|
|
101
|
+
key: string;
|
|
102
|
+
/** How to compare it — the shared comparison vocabulary, see `OPERATORS`. */
|
|
103
|
+
op: Operator;
|
|
104
|
+
/**
|
|
105
|
+
* What to compare it to. Left out by the four operators that read presence
|
|
106
|
+
* rather than content (`exists`, `notExists`, `isEmpty`, `isNotEmpty`).
|
|
107
|
+
*/
|
|
108
|
+
value?: string | number | boolean;
|
|
109
|
+
}
|
|
110
|
+
export interface IConfigFieldBase {
|
|
111
|
+
key: string;
|
|
112
|
+
label: LocalizedString;
|
|
113
|
+
type: ConfigType;
|
|
114
|
+
description?: LocalizedString;
|
|
115
|
+
required?: boolean;
|
|
116
|
+
default?: unknown;
|
|
117
|
+
placeholder?: LocalizedString;
|
|
118
|
+
expressionAllowed?: boolean;
|
|
119
|
+
multiline?: boolean;
|
|
120
|
+
validation?: IConfigValidation;
|
|
121
|
+
options?: IConfigOption[];
|
|
122
|
+
/**
|
|
123
|
+
* When true, a *known* field's `options` are resolved at author time via the
|
|
124
|
+
* node's `loadOptions` (a live/dependent dropdown) instead of a static
|
|
125
|
+
* `options[]`. Applies to `select` / `multiselect`; left unset for ordinary
|
|
126
|
+
* static fields.
|
|
127
|
+
*
|
|
128
|
+
* Independent of `type: 'dynamic-schema'`: that field type triggers
|
|
129
|
+
* `resolveConfigSchema` on its own and does NOT require `dynamic: true`.
|
|
130
|
+
*/
|
|
131
|
+
dynamic?: boolean;
|
|
132
|
+
/**
|
|
133
|
+
* Config keys whose values drive this field's dynamic resolution. The editor
|
|
134
|
+
* re-resolves when one of them changes. A key listed here is
|
|
135
|
+
* *dependency-driving* and MUST be a literal (it may not set
|
|
136
|
+
* `expressionAllowed`), so its value is known at author time.
|
|
137
|
+
*/
|
|
138
|
+
dependsOn?: string[];
|
|
139
|
+
/**
|
|
140
|
+
* When this field applies at all. The editor draws it only while the
|
|
141
|
+
* condition holds, so a node says once in its manifest what it would
|
|
142
|
+
* otherwise have to explain in prose under every affected field.
|
|
143
|
+
*
|
|
144
|
+
* Not to be confused with `dependsOn`, which sits beside it and does a
|
|
145
|
+
* different thing: `dependsOn` re-*resolves* a dynamic field's options when
|
|
146
|
+
* another field changes, and never decides whether the field is drawn.
|
|
147
|
+
*
|
|
148
|
+
* Left unset, the field always applies — so this is additive: a node that
|
|
149
|
+
* says nothing behaves as it always did, and so does an editor that does not
|
|
150
|
+
* know the key.
|
|
151
|
+
*/
|
|
152
|
+
showIf?: IShowIfCondition;
|
|
153
|
+
/**
|
|
154
|
+
* What a value mapped into this field from an earlier step has to be — `'array'`
|
|
155
|
+
* for a setting that wants the list a previous node produced (PO-594).
|
|
156
|
+
*
|
|
157
|
+
* The editor uses it to put the earlier steps' outputs of that kind first in the
|
|
158
|
+
* `{ }` list, mark them, and offer the single matching one as a one-click answer
|
|
159
|
+
* under an empty field. So the author does not have to know what a previous node
|
|
160
|
+
* returns, or which of its fields is the list.
|
|
161
|
+
*
|
|
162
|
+
* Not to be confused with `type: 'array'`, which draws the setting itself as
|
|
163
|
+
* repeating rows the author fills in by hand. Only meaningful together with
|
|
164
|
+
* `expressionAllowed`. Left unset, every output is offered alike, as before.
|
|
165
|
+
*/
|
|
166
|
+
accepts?: DataType;
|
|
167
|
+
/**
|
|
168
|
+
* Only meaningful when `type === 'credentials-ref'`: the namespaced slug(s) of
|
|
169
|
+
* the credential type(s) this field accepts (e.g. `revenexx:smtp`). The editor
|
|
170
|
+
* lists tenant credential instances of these type(s); the blob stores the
|
|
171
|
+
* chosen instance UUID. Pass a single string when only one type is accepted,
|
|
172
|
+
* or an array to let the field accept instances of any of several types (e.g.
|
|
173
|
+
* a node that works with both an OAuth and an API-token credential).
|
|
174
|
+
*/
|
|
175
|
+
credentialType?: string | string[];
|
|
176
|
+
/**
|
|
177
|
+
* Only meaningful when `type === 'state-ref'`: which kind of state namespace
|
|
178
|
+
* this field accepts (PO-374). The editor lists the workflow's declared
|
|
179
|
+
* namespaces of that role — and lets the author declare a new one without
|
|
180
|
+
* leaving the node — and the blob stores the chosen NAME.
|
|
181
|
+
*
|
|
182
|
+
* Declare the field rather than hardcoding a namespace name in `execute`: a
|
|
183
|
+
* literal is a contract nothing checks, and the mismatch surfaces as a 403 in
|
|
184
|
+
* the first production run rather than as an empty picker while authoring. It
|
|
185
|
+
* also narrows what the node may reach — a namespace is reachable only from
|
|
186
|
+
* the node whose config names it.
|
|
187
|
+
*/
|
|
188
|
+
stateRole?: 'mapping' | 'cursor' | 'dedupe' | 'digest';
|
|
189
|
+
}
|
|
190
|
+
export interface IConfigField extends IConfigFieldBase {
|
|
191
|
+
/**
|
|
192
|
+
* The sub-settings of a field that holds a group of them (`type: 'object'`).
|
|
193
|
+
*
|
|
194
|
+
* A full `IConfigField`, not the base. `IConfigFieldBase` already carries
|
|
195
|
+
* `options`, `default`, `required`, `description` and `showIf`, so those were
|
|
196
|
+
* never what a nested setting was missing — `properties` and `items` were: the
|
|
197
|
+
* base has neither, so a sub-setting could not itself group or repeat without
|
|
198
|
+
* a cast. The manifest schema permits that nesting and the engine's config
|
|
199
|
+
* walker reads nested fields as first-class; the type was the only place
|
|
200
|
+
* stopping at one level (PO-436).
|
|
201
|
+
*/
|
|
202
|
+
properties?: IConfigField[];
|
|
203
|
+
/**
|
|
204
|
+
* What one entry of a list field (`type: 'array'`) looks like.
|
|
205
|
+
*
|
|
206
|
+
* Full `IConfigField` for the same reason as `properties`, and this is the one
|
|
207
|
+
* that bit: `items.type: 'object'` plus `items.properties` is the repeating row
|
|
208
|
+
* every mapping-style setting is built from, and `items` typed as the base had
|
|
209
|
+
* no `properties` to give it. `SwitchNode` in integrations-nodes-core ends its
|
|
210
|
+
* items block with `as IConfigField` for exactly that reason.
|
|
211
|
+
*/
|
|
212
|
+
items?: IConfigField;
|
|
213
|
+
}
|
|
214
|
+
export interface INodeDescription {
|
|
215
|
+
slug: string;
|
|
216
|
+
version: string;
|
|
217
|
+
category: NodeCategory;
|
|
218
|
+
name: LocalizedString;
|
|
219
|
+
description?: LocalizedString;
|
|
220
|
+
/** 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. */
|
|
221
|
+
icon?: string;
|
|
222
|
+
/**
|
|
223
|
+
* Curated node-picker group path, outermost first (max 4 levels), e.g.
|
|
224
|
+
* `[{ en: 'Business Central' }, { en: 'Sales Orders' }]`. Optional —
|
|
225
|
+
* pickers without it fall back to package/category grouping.
|
|
226
|
+
*/
|
|
227
|
+
groups?: LocalizedString[];
|
|
228
|
+
/**
|
|
229
|
+
* Where this node stands inside its innermost palette folder, among the nodes
|
|
230
|
+
* of the same package: lower comes first. Optional — the editor draws nodes
|
|
231
|
+
* with a position before those without, and those without in the order the
|
|
232
|
+
* package lists them in `NODES`, so a package that lists its main node first
|
|
233
|
+
* needs no position at all. Use it when the array order is not the palette
|
|
234
|
+
* order, e.g. when `NODES` is sorted for another reason. It orders nodes only
|
|
235
|
+
* within one package; where a folder takes nodes from several packages, the
|
|
236
|
+
* order between packages is the editor's.
|
|
237
|
+
*/
|
|
238
|
+
paletteOrder?: number;
|
|
239
|
+
/** Associated images (screenshots, logos, banners) shipped with the package. */
|
|
240
|
+
images?: IImage[];
|
|
241
|
+
inputs: Record<string, IInputPort>;
|
|
242
|
+
/**
|
|
243
|
+
* Output ports the workflow editor wires to downstream nodes. An empty array
|
|
244
|
+
* marks a **terminal node**: the path ends here and no edge may leave it —
|
|
245
|
+
* the mirror image of `inputs: {}` on a trigger. `StopAndErrorNode` is the
|
|
246
|
+
* canonical case; it always throws, so there is no success path to wire, and
|
|
247
|
+
* a port that can never fire would leave a dead handle on the canvas. The
|
|
248
|
+
* key stays required so terminality is declared, not forgotten.
|
|
249
|
+
*/
|
|
250
|
+
outputs: IOutputPort[];
|
|
251
|
+
config?: IConfigField[];
|
|
252
|
+
}
|
|
253
|
+
export interface INodeContext {
|
|
254
|
+
signal: AbortSignal;
|
|
255
|
+
logger: {
|
|
256
|
+
info(message: string, meta?: Record<string, unknown>): void;
|
|
257
|
+
warn(message: string, meta?: Record<string, unknown>): void;
|
|
258
|
+
error(message: string, meta?: Record<string, unknown>): void;
|
|
259
|
+
};
|
|
260
|
+
secrets: {
|
|
261
|
+
get(key: string): Promise<string>;
|
|
262
|
+
};
|
|
263
|
+
/**
|
|
264
|
+
* Resolve the access data of a credential instance the node references via a
|
|
265
|
+
* `credentials-ref` config field. The runtime fulfils this from the
|
|
266
|
+
* credentials broker at execution time (inside the Temporal activity), so the
|
|
267
|
+
* returned value — e.g. a short-lived access token — is always current and
|
|
268
|
+
* never persisted into workflow history.
|
|
269
|
+
*/
|
|
270
|
+
credentials: {
|
|
271
|
+
get(credentialsId: string): Promise<Record<string, unknown>>;
|
|
272
|
+
};
|
|
273
|
+
/**
|
|
274
|
+
* The tenant state store: what this workflow already knows from earlier runs
|
|
275
|
+
* (PO-374). Every call names a namespace the workflow declared in its
|
|
276
|
+
* `state[]` block; anything else is refused by the engine, so a node cannot
|
|
277
|
+
* reach state its workflow did not ask for.
|
|
278
|
+
*/
|
|
279
|
+
state: INodeState;
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* The four things a workflow can remember between runs, and the reason each is
|
|
283
|
+
* its own operation rather than a generic get/set: the role decides *when* a
|
|
284
|
+
* write becomes visible, and that decision is the engine's to make, not the
|
|
285
|
+
* node author's.
|
|
286
|
+
*
|
|
287
|
+
* The four roles are named **mapping**, **cursor**, **dedupe** and **digest** —
|
|
288
|
+
* those are the values a `state-ref` field's `stateRole` takes. `claim` below is
|
|
289
|
+
* the *operation* on a dedupe namespace, not a role of its own.
|
|
290
|
+
*
|
|
291
|
+
* - **mapping** and **dedupe** take effect immediately. A correlation discarded
|
|
292
|
+
* because the run failed afterwards is how the next run creates a duplicate
|
|
293
|
+
* in the target system; a claim invisible to other runs protects against
|
|
294
|
+
* nothing.
|
|
295
|
+
* - **cursor** and **digest** are staged and adopted only when the run
|
|
296
|
+
* completes. A watermark that advances on a failed run leaves exactly the gap
|
|
297
|
+
* it exists to prevent, and a digest may only count once the write it
|
|
298
|
+
* describes actually went through.
|
|
299
|
+
*
|
|
300
|
+
* At author time — the editor's "test this node" button — the store is
|
|
301
|
+
* **read-only**: a rehearsal must not leave correlations behind that a later
|
|
302
|
+
* production run would treat as truth. The write calls reject there.
|
|
303
|
+
*/
|
|
304
|
+
export interface INodeState {
|
|
305
|
+
/** Correlate an id on each side of an integration (PIM article ↔ ERP number). */
|
|
306
|
+
mapping: {
|
|
307
|
+
/**
|
|
308
|
+
* The partner id of a correlation, or `null` when it is unknown — which is
|
|
309
|
+
* the usual way to decide between creating and updating in the target
|
|
310
|
+
* system.
|
|
311
|
+
*
|
|
312
|
+
* `side` says which side of the pair `key` is on, not which side to return:
|
|
313
|
+
* the default `'left'` matches `key` against the left id and answers with
|
|
314
|
+
* the right one, and `'right'` does the reverse. It never searches both, so
|
|
315
|
+
* a lookup on the wrong side answers `null` rather than falling back — and
|
|
316
|
+
* `null` on the create path is what makes the target record a second time.
|
|
317
|
+
*/
|
|
318
|
+
get(namespace: string, key: string, side?: 'left' | 'right'): Promise<string | null>;
|
|
319
|
+
/**
|
|
320
|
+
* Record a correlation. Takes effect immediately, and re-pointing one side
|
|
321
|
+
* of an existing pair is refused: an id correlated two ways is always a bug,
|
|
322
|
+
* and no later sync can untangle it.
|
|
323
|
+
*/
|
|
324
|
+
put(namespace: string, left: string, right: string, metadata?: Record<string, unknown>): Promise<void>;
|
|
325
|
+
};
|
|
326
|
+
/** How far an incremental sync has read. */
|
|
327
|
+
cursor: {
|
|
328
|
+
/**
|
|
329
|
+
* The committed watermark — an arbitrary JSON value such as
|
|
330
|
+
* `{ updatedAfter }` or a provider page token — or `undefined` when the
|
|
331
|
+
* sync has never run. A value staged by *this* run is not visible yet.
|
|
332
|
+
*/
|
|
333
|
+
get(namespace: string, partitionKey?: string): Promise<unknown>;
|
|
334
|
+
/**
|
|
335
|
+
* Stage a watermark. Adopted only if the run completes, so a failed run
|
|
336
|
+
* leaves the previous value in place and the next run picks the gap up.
|
|
337
|
+
* `partitionKey` separates e.g. one shop or company code from another.
|
|
338
|
+
*/
|
|
339
|
+
set(namespace: string, value: unknown, partitionKey?: string): Promise<void>;
|
|
340
|
+
};
|
|
341
|
+
/**
|
|
342
|
+
* Claim a key for the duration of its TTL. `true` means this caller got it;
|
|
343
|
+
* `false` means the key is held and this delivery is a duplicate — the normal
|
|
344
|
+
* answer under at-least-once delivery, and a value to branch on rather than an
|
|
345
|
+
* error.
|
|
346
|
+
*
|
|
347
|
+
* **A claim outlives the attempt that made it.** The holder is not identified:
|
|
348
|
+
* a claim is taken over only once it has expired, so a retry of the very
|
|
349
|
+
* attempt that claimed the key — after a transient failure that happened
|
|
350
|
+
* *after* the claim — is also told `false`. Treating that as a duplicate drops
|
|
351
|
+
* the delivery for the rest of the TTL, which makes `ttlSeconds` the retry
|
|
352
|
+
* window as much as the duplicate-suppression window. Choose it against both,
|
|
353
|
+
* and do not claim earlier in the node than necessary.
|
|
354
|
+
*
|
|
355
|
+
* `ttlSeconds` defaults to 604800 (seven days) and is accepted between 1 and
|
|
356
|
+
* 31536000.
|
|
357
|
+
*/
|
|
358
|
+
claim(namespace: string, key: string, opts?: {
|
|
359
|
+
ttlSeconds?: number;
|
|
360
|
+
}): Promise<boolean>;
|
|
361
|
+
/** Skip expensive writes for entities that have not changed. */
|
|
362
|
+
digest: {
|
|
363
|
+
/** Whether the entity still carries this hash — i.e. the write can be skipped. */
|
|
364
|
+
unchanged(namespace: string, entityKey: string, digest: string): Promise<boolean>;
|
|
365
|
+
/** Stage a hash; adopted only if the run completes. Call it *after* the write. */
|
|
366
|
+
set(namespace: string, entityKey: string, digest: string): Promise<void>;
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
export interface INodeResult {
|
|
370
|
+
outputs: Record<string, unknown>;
|
|
371
|
+
branch?: string;
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Context handed to a node's *author-time* resolvers (`loadOptions`,
|
|
375
|
+
* `resolveConfigSchema`, `resolveOutputs`). These run in the node-runtime host
|
|
376
|
+
* (a Node side-container) while a user is editing the workflow — never in
|
|
377
|
+
* `execute`. The result is snapshotted into the workflow blob at save, so the
|
|
378
|
+
* runtime never calls these (PO-143).
|
|
379
|
+
*/
|
|
380
|
+
export interface INodeAuthorContext {
|
|
381
|
+
signal: AbortSignal;
|
|
382
|
+
logger: {
|
|
383
|
+
info(message: string, meta?: Record<string, unknown>): void;
|
|
384
|
+
warn(message: string, meta?: Record<string, unknown>): void;
|
|
385
|
+
error(message: string, meta?: Record<string, unknown>): void;
|
|
386
|
+
};
|
|
387
|
+
/** The user's current partial config values. */
|
|
388
|
+
config: Record<string, unknown>;
|
|
389
|
+
/**
|
|
390
|
+
* Lazily resolve a `secret-ref` config field's value (a key in the tenant
|
|
391
|
+
* secret store → a single string). Mirrors {@link INodeContext.secrets} so a
|
|
392
|
+
* resolver that authenticates via a secret-ref uses the same call it would in
|
|
393
|
+
* `execute`. Resolves against the internal secret endpoint; only the keys the
|
|
394
|
+
* resolver actually asks for are fetched.
|
|
395
|
+
*/
|
|
396
|
+
secrets: {
|
|
397
|
+
get(key: string): Promise<string>;
|
|
398
|
+
};
|
|
399
|
+
/**
|
|
400
|
+
* Lazily resolve a `credentials-ref` instance's access data via the
|
|
401
|
+
* credentials broker (id → structured material, e.g. `{ accessToken }`).
|
|
402
|
+
* Mirrors {@link INodeContext.credentials}.
|
|
403
|
+
*/
|
|
404
|
+
credentials: {
|
|
405
|
+
get(credentialsId: string): Promise<Record<string, unknown>>;
|
|
406
|
+
};
|
|
407
|
+
/** Preferred locale for resolved labels, when the caller supplies one. */
|
|
408
|
+
locale?: string;
|
|
409
|
+
/**
|
|
410
|
+
* The operator's type-to-search term for the option list being resolved.
|
|
411
|
+
* Only set for `loadOptions` calls. Providers may pass it to a server-side
|
|
412
|
+
* search endpoint or use it to filter the options they return.
|
|
413
|
+
*/
|
|
414
|
+
search?: string;
|
|
415
|
+
}
|
|
416
|
+
export interface INode {
|
|
417
|
+
description: INodeDescription;
|
|
418
|
+
execute(ctx: INodeContext, inputs: Record<string, unknown>): Promise<INodeResult>;
|
|
419
|
+
/**
|
|
420
|
+
* Author-time: resolve the options of a `dynamic` field (a live/dependent
|
|
421
|
+
* dropdown). `fieldKey` is the config key being resolved. Optional — declare
|
|
422
|
+
* it only for nodes with `dynamic` `select`/`multiselect` fields.
|
|
423
|
+
*/
|
|
424
|
+
loadOptions?(ctx: INodeAuthorContext, fieldKey: string): Promise<IConfigOption[]>;
|
|
425
|
+
/**
|
|
426
|
+
* Author-time: resolve the flat set of typed config fields that replaces a
|
|
427
|
+
* `type: 'dynamic-schema'` marker (e.g. an API's parameters once app + API
|
|
428
|
+
* are chosen). Returns fields in the same config-field grammar; the node is
|
|
429
|
+
* responsible for flattening any nested API shape into flat keys.
|
|
430
|
+
*/
|
|
431
|
+
resolveConfigSchema?(ctx: INodeAuthorContext): Promise<IConfigField[]>;
|
|
432
|
+
/**
|
|
433
|
+
* Author-time: resolve a generic output-port set for an output marked
|
|
434
|
+
* `resolveOutputs` (e.g. ports derived from a connected resource's schema).
|
|
435
|
+
*/
|
|
436
|
+
resolveOutputs?(ctx: INodeAuthorContext): Promise<IOutputPort[]>;
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* Optional capability interface for nodes that iterate over a collection.
|
|
440
|
+
* Implement this alongside {@link INode} to signal to the runtime that this
|
|
441
|
+
* node drives iteration — the runtime will call {@link extractItems} instead
|
|
442
|
+
* of relying on slug-based detection.
|
|
443
|
+
*
|
|
444
|
+
* This interface is the designated dispatch point for future child-workflow
|
|
445
|
+
* execution: once per-iteration isolation is needed, the runtime can swap the
|
|
446
|
+
* inline loop for `executeChild` calls without any changes to the node or SDK.
|
|
447
|
+
*/
|
|
448
|
+
export interface INodeWithIteration {
|
|
449
|
+
/**
|
|
450
|
+
* Extracts the array of items to iterate over from the resolved inputs and
|
|
451
|
+
* config. Must be pure and synchronous — it runs inside a Temporal Activity.
|
|
452
|
+
*/
|
|
453
|
+
extractItems(inputs: Record<string, unknown>, config: Record<string, unknown>): unknown[];
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
456
|
+
* Type guard that checks whether a node implements {@link INodeWithIteration}.
|
|
457
|
+
*/
|
|
458
|
+
export declare function isNodeWithIteration(node: INode): node is INode & INodeWithIteration;
|
|
459
|
+
/**
|
|
460
|
+
* The authentication strategy a credential type uses. Determines which SDK
|
|
461
|
+
* base class a concrete credential extends and how the broker resolves it.
|
|
462
|
+
*/
|
|
463
|
+
export type CredentialAuthKind = 'static' | 'api-key' | 'basic' | 'oauth2-client-credentials' | 'oauth2-authcode';
|
|
464
|
+
/**
|
|
465
|
+
* Field types for the connection parameters a credential type declares. A
|
|
466
|
+
* superset-subset of {@link ConfigType}: scalars plus `secret` for values that
|
|
467
|
+
* must be masked in the UI and never returned in plaintext by the public API.
|
|
468
|
+
*/
|
|
469
|
+
export type CredentialFieldType = 'string' | 'number' | 'boolean' | 'select' | 'secret';
|
|
470
|
+
export interface ICredentialField {
|
|
471
|
+
key: string;
|
|
472
|
+
label: LocalizedString;
|
|
473
|
+
type: CredentialFieldType;
|
|
474
|
+
description?: LocalizedString;
|
|
475
|
+
required?: boolean;
|
|
476
|
+
default?: unknown;
|
|
477
|
+
placeholder?: LocalizedString;
|
|
478
|
+
validation?: IConfigValidation;
|
|
479
|
+
options?: IConfigOption[];
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* The published contract of a credential type: how the editor renders its
|
|
483
|
+
* config form and which auth strategy the broker applies. The imperative
|
|
484
|
+
* `test`/`resolve` logic lives in the bundled {@link ICredential} code, not in
|
|
485
|
+
* this description.
|
|
486
|
+
*/
|
|
487
|
+
export interface ICredentialDescription {
|
|
488
|
+
/** Stable namespaced identifier `<namespace>:<slug>` (e.g. `revenexx:smtp`). */
|
|
489
|
+
slug: string;
|
|
490
|
+
version: string;
|
|
491
|
+
name: LocalizedString;
|
|
492
|
+
description?: LocalizedString;
|
|
493
|
+
/** 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. */
|
|
494
|
+
icon?: string;
|
|
495
|
+
/** Associated images (screenshots, logos, banners) shipped with the package. */
|
|
496
|
+
images?: IImage[];
|
|
497
|
+
authKind: CredentialAuthKind;
|
|
498
|
+
fields: ICredentialField[];
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* Runtime context handed to a credential's `test`/`resolve`. Runs in the
|
|
502
|
+
* credentials broker (a Node side-container), never in workflow code.
|
|
503
|
+
*/
|
|
504
|
+
export interface ICredentialContext {
|
|
505
|
+
signal: AbortSignal;
|
|
506
|
+
logger: {
|
|
507
|
+
info(message: string, meta?: Record<string, unknown>): void;
|
|
508
|
+
warn(message: string, meta?: Record<string, unknown>): void;
|
|
509
|
+
error(message: string, meta?: Record<string, unknown>): void;
|
|
510
|
+
};
|
|
511
|
+
/**
|
|
512
|
+
* Persist updated durable credentials (e.g. a rotated `refresh_token`) back
|
|
513
|
+
* to storage. The broker wires this to `PATCH /v1/internal/credentials/{id}/durable-creds`.
|
|
514
|
+
* Absent during pre-save tests where no instance exists yet.
|
|
515
|
+
*/
|
|
516
|
+
persistDurableCreds?(durableCreds: Record<string, unknown>): Promise<void>;
|
|
517
|
+
}
|
|
518
|
+
export interface ICredentialTestResult {
|
|
519
|
+
ok: boolean;
|
|
520
|
+
message?: string;
|
|
521
|
+
}
|
|
522
|
+
export interface ICredentialResolveResult {
|
|
523
|
+
/** The access data handed to the node (e.g. `{ host, port, user, password }` or `{ accessToken }`). */
|
|
524
|
+
credentials: Record<string, unknown>;
|
|
525
|
+
/** ISO-8601 expiry of the resolved access data; absent for non-expiring (static) credentials. */
|
|
526
|
+
expiresAt?: string;
|
|
527
|
+
}
|
|
528
|
+
/**
|
|
529
|
+
* A credential type implementation. `config` is the validated, user-entered
|
|
530
|
+
* field map; `durableCreds` holds system-managed long-lived secrets (e.g. a
|
|
531
|
+
* `refresh_token` obtained via 3-legged OAuth) and is `null` until they exist.
|
|
532
|
+
*/
|
|
533
|
+
export interface ICredential {
|
|
534
|
+
description: ICredentialDescription;
|
|
535
|
+
test(ctx: ICredentialContext, config: Record<string, unknown>): Promise<ICredentialTestResult>;
|
|
536
|
+
resolve(ctx: ICredentialContext, config: Record<string, unknown>, durableCreds: Record<string, unknown> | null): Promise<ICredentialResolveResult>;
|
|
537
|
+
}
|
|
538
|
+
/**
|
|
539
|
+
* Optional capability for `oauth2-authcode` credentials that need the
|
|
540
|
+
* interactive (3-legged) consent dance. The broker exposes these via
|
|
541
|
+
* `/oauth/authorize-url` and `/oauth/exchange-code`; Laravel proxies them.
|
|
542
|
+
*/
|
|
543
|
+
export interface ICredentialOAuthAuthorize {
|
|
544
|
+
buildAuthorizeUrl(ctx: ICredentialContext, config: Record<string, unknown>, params: {
|
|
545
|
+
redirectUri: string;
|
|
546
|
+
state: string;
|
|
547
|
+
}): Promise<{
|
|
548
|
+
authorizeUrl: string;
|
|
549
|
+
codeVerifier?: string;
|
|
550
|
+
}>;
|
|
551
|
+
exchangeCode(ctx: ICredentialContext, config: Record<string, unknown>, params: {
|
|
552
|
+
code: string;
|
|
553
|
+
redirectUri: string;
|
|
554
|
+
codeVerifier?: string;
|
|
555
|
+
}): Promise<{
|
|
556
|
+
durableCreds: Record<string, unknown>;
|
|
557
|
+
}>;
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
560
|
+
* Type guard for credentials that implement the 3-legged OAuth consent flow.
|
|
561
|
+
*/
|
|
562
|
+
export declare function isOAuthAuthorizeCredential(credential: ICredential): credential is ICredential & ICredentialOAuthAuthorize;
|
|
563
|
+
/** Difficulty level surfaced in the template gallery. */
|
|
564
|
+
export type TemplateLevel = 'beginner' | 'intermediate' | 'advanced';
|
|
565
|
+
/** Trigger kind a template can ship; mirrors the server's `TriggerType`. */
|
|
566
|
+
export type TemplateTriggerType = 'manual' | 'schedule' | 'webhook' | 'event';
|
|
567
|
+
/**
|
|
568
|
+
* A trigger a template instantiates alongside its workflow. The `handle` is a
|
|
569
|
+
* stable UUID the workflow blob's edges reference via `from.nodeId`. Per-type
|
|
570
|
+
* `config` shapes: `schedule` → `{ cron, timezone? }`, `webhook` →
|
|
571
|
+
* `{ method, secretRef?, public? }`, `event` → `{ subject }`, `manual` → none.
|
|
572
|
+
* The integrations server deep-validates `config` per `type` on publish.
|
|
573
|
+
*/
|
|
574
|
+
export interface ITemplateTrigger {
|
|
575
|
+
/** Stable UUID the blob's edges reference via `from.nodeId`. */
|
|
576
|
+
handle: string;
|
|
577
|
+
type: TemplateTriggerType;
|
|
578
|
+
name?: string;
|
|
579
|
+
config?: Record<string, unknown>;
|
|
580
|
+
active?: boolean;
|
|
581
|
+
}
|
|
582
|
+
/**
|
|
583
|
+
* A workflow template a node package publishes: a ready-made workflow blueprint
|
|
584
|
+
* the editor offers as a starting point. Unlike {@link INode}/{@link ICredential}
|
|
585
|
+
* a template carries no executable code — it is plain data, so a package exports
|
|
586
|
+
* its `ITemplateDescription`s directly (no class wrapper).
|
|
587
|
+
*
|
|
588
|
+
* The `definition` is a workflow blob authored against the workflow-blob grammar
|
|
589
|
+
* named by `blobVersion`; the integrations server validates it on publish.
|
|
590
|
+
*/
|
|
591
|
+
export interface ITemplateDescription {
|
|
592
|
+
/** Stable namespaced identifier `<namespace>:<slug>` (e.g. `revenexx:slack-to-crm`). */
|
|
593
|
+
slug: string;
|
|
594
|
+
version: string;
|
|
595
|
+
/** Free-form grouping label shown in the gallery (e.g. `sales`). */
|
|
596
|
+
category: string;
|
|
597
|
+
level: TemplateLevel;
|
|
598
|
+
name: LocalizedString;
|
|
599
|
+
/** One-line summary shown on the template card. */
|
|
600
|
+
shortDescription?: LocalizedString;
|
|
601
|
+
/** Long-form description in Markdown. */
|
|
602
|
+
description?: LocalizedString;
|
|
603
|
+
/** 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. */
|
|
604
|
+
icon?: string;
|
|
605
|
+
/** Free-form industry tags (e.g. `any`, `medical`). */
|
|
606
|
+
industries?: string[];
|
|
607
|
+
/** Free-form vendor tags (e.g. `pipedrive`, `slack`). */
|
|
608
|
+
vendors?: string[];
|
|
609
|
+
/** Associated images (screenshots, logos, banners) shipped with the package. */
|
|
610
|
+
images?: IImage[];
|
|
611
|
+
/** Workflow-blob schema version `definition` targets (e.g. `v0-draft`). */
|
|
612
|
+
blobVersion: string;
|
|
613
|
+
/** The workflow blob instantiated when a user picks this template. */
|
|
614
|
+
definition: Record<string, unknown>;
|
|
615
|
+
/**
|
|
616
|
+
* Triggers instantiated alongside the workflow (their `handle`s are
|
|
617
|
+
* referenced by `definition`'s edges). Omit for a manual-only template.
|
|
618
|
+
*/
|
|
619
|
+
triggers?: ITemplateTrigger[];
|
|
620
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@revenexx/integrations-node-sdk",
|
|
3
|
-
"version": "1.9.
|
|
3
|
+
"version": "1.9.1",
|
|
4
4
|
"description": "TypeScript interfaces and utilities for Revenexx integration nodes",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "revenexx GmbH",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"provenance": true
|
|
43
43
|
},
|
|
44
44
|
"scripts": {
|
|
45
|
-
"build": "tsup",
|
|
45
|
+
"build": "tsup && tsc -p tsconfig.build.json",
|
|
46
46
|
"dev": "tsup --watch",
|
|
47
47
|
"test": "node --import tsx --test \"src/**/*.test.ts\"",
|
|
48
48
|
"spec:check": "node scripts/spec-check.mjs",
|
|
@@ -58,9 +58,9 @@
|
|
|
58
58
|
"@biomejs/biome": "^2.5.10",
|
|
59
59
|
"@changesets/cli": "^3.0.3",
|
|
60
60
|
"@types/node": "^24.0.0",
|
|
61
|
-
"tsup": "^
|
|
61
|
+
"tsup": "^9.0.0",
|
|
62
62
|
"tsx": "^4.0.0",
|
|
63
|
-
"typescript": "^
|
|
63
|
+
"typescript": "^7.0.2"
|
|
64
64
|
},
|
|
65
65
|
"dependencies": {
|
|
66
66
|
"ipaddr.js": "2.5.0"
|