@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/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 };