@revenexx/integrations-node-sdk 0.11.0 → 0.12.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/LICENSE +21 -0
- package/README.md +283 -0
- package/dist/{chunk-NJJ7FHGD.js → chunk-X5HI7WPI.js} +0 -1
- package/dist/cli.cjs +0 -1
- package/dist/cli.js +1 -2
- package/dist/index.cjs +101 -15
- package/dist/index.d.cts +32 -7
- package/dist/index.d.ts +32 -7
- package/dist/index.js +93 -15
- package/package.json +33 -4
- package/dist/chunk-NJJ7FHGD.js.map +0 -1
- package/dist/cli.cjs.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/index.cjs.map +0 -1
- package/dist/index.js.map +0 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 revenexx GmbH
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
# @revenexx/integrations-node-sdk
|
|
2
|
+
|
|
3
|
+
The shared TypeScript contract library for **Revenexx integration nodes**. It
|
|
4
|
+
defines the interfaces every integration node and credential must implement, and
|
|
5
|
+
ships the small helper toolchain the node registry uses to build manifests.
|
|
6
|
+
|
|
7
|
+
It has **no runtime dependencies** and contains no business logic beyond the
|
|
8
|
+
manifest helpers — it exists purely to share one contract between the node
|
|
9
|
+
packages and the workflow engine that runs them.
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
integrations-node-sdk ← this package (types & helpers only)
|
|
13
|
+
└── integrations-nodes-core ← implements INode, ships dist/manifest.json
|
|
14
|
+
└── integrations-worker ← registers nodes, executes workflows
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install @revenexx/integrations-node-sdk
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Published to the public npm registry under the `@revenexx` scope — no registry
|
|
24
|
+
configuration or auth token needed.
|
|
25
|
+
|
|
26
|
+
## What's inside
|
|
27
|
+
|
|
28
|
+
| Module | Purpose |
|
|
29
|
+
| --- | --- |
|
|
30
|
+
| `types` | All interfaces and union types: the node contract (`INode`, `INodeDescription`, `INodeContext`, `INodeResult`, `IConfigField`, `INodeWithIteration`, …), the credential contract (`ICredential`, `ICredentialDescription`, …) and the template contract (`ITemplateDescription`, `ITemplateTrigger`). |
|
|
31
|
+
| `credentials` | Abstract base classes for credential authors: `BaseCredential`, `SimpleValueCredential`, `ApiKeyCredential`, `BasicAuthCredential`, `OAuth2ClientCredentialsCredential`, `OAuth2AuthCodeCredential`. Concrete credentials `extend` one of these. |
|
|
32
|
+
| `localized` | `normalizeLocalized` — reduce a `LocalizedString` (`string \| Record<string, string>`) to a single plain string. |
|
|
33
|
+
| `credentialType` | `normalizeCredentialType` — normalise a credential-type reference (`string \| string[] \| undefined`) to `string[]`. |
|
|
34
|
+
| `errors` | `NodeError` — typed error class for unexpected/system-level failures thrown inside `execute`. |
|
|
35
|
+
| `extract` | `extractManifest` / `extractManifests` (nodes) and `extractCredentialManifest` / `extractCredentialManifests` (credentials) — pull descriptions off instances without executing them. |
|
|
36
|
+
| `manifest` | `buildManifest` / `MANIFEST_VERSION` — wrap node, credential and template descriptions in the `{ manifestVersion, nodes, credentials, templates }` envelope the registry expects. |
|
|
37
|
+
|
|
38
|
+
Everything is re-exported from the package root, and the package ships dual
|
|
39
|
+
ESM/CJS output plus `.d.ts` types.
|
|
40
|
+
|
|
41
|
+
## Usage
|
|
42
|
+
|
|
43
|
+
### Authoring a node
|
|
44
|
+
|
|
45
|
+
Implement `INode`: an instance `description` property (the metadata the registry
|
|
46
|
+
publishes) plus an `execute` method. A few conventions the engine relies on:
|
|
47
|
+
|
|
48
|
+
- **`slug` is namespaced** — `<namespace>:<slug>`, e.g. `revenexx:text-replace`.
|
|
49
|
+
It is the stable identity of the node across versions; not a dotted path.
|
|
50
|
+
- **Every output port carries a `name`.** `execute` returns an `outputs` map
|
|
51
|
+
keyed by that port `name`, and sets `branch` to the port that fired. The key,
|
|
52
|
+
the `branch` and the declared `name` must match — that triple is how the
|
|
53
|
+
engine routes the result to the next node.
|
|
54
|
+
- **Config and inputs arrive in the same map.** The engine resolves each
|
|
55
|
+
declared `config` field (keyed by its `key`) and the input ports (`'in'`, plus
|
|
56
|
+
any named fan-in ports) into the single `inputs: Record<string, unknown>`
|
|
57
|
+
handed to `execute`. So `inputs['in']` is the upstream payload and
|
|
58
|
+
`inputs['<config-key>']` is the resolved config value.
|
|
59
|
+
- **`ctx.signal` is always provided** — propagate it to every I/O call.
|
|
60
|
+
|
|
61
|
+
#### Transform node — single input, single output
|
|
62
|
+
|
|
63
|
+
A `transform` node with one `'in'` port and one named `out` port. Mirrors
|
|
64
|
+
`TextReplaceNode` from `integrations-nodes-core`:
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
import type { INode, INodeContext, INodeDescription, INodeResult } from '@revenexx/integrations-node-sdk';
|
|
68
|
+
import { NodeError } from '@revenexx/integrations-node-sdk';
|
|
69
|
+
|
|
70
|
+
export class UppercaseNode implements INode {
|
|
71
|
+
description = {
|
|
72
|
+
slug: 'revenexx:text-uppercase',
|
|
73
|
+
version: '1.0.0',
|
|
74
|
+
category: 'transform',
|
|
75
|
+
name: { en: 'Uppercase', de: 'Großschreiben' },
|
|
76
|
+
description: { en: 'Upper-cases a string read from the input.' },
|
|
77
|
+
icon: 'mdi:format-letter-case-upper',
|
|
78
|
+
inputs: { in: { dataType: 'string', required: true } },
|
|
79
|
+
outputs: [{ name: 'out', kind: 'default', dataType: 'string', label: { en: 'Result', de: 'Ergebnis' } }],
|
|
80
|
+
} satisfies INodeDescription;
|
|
81
|
+
|
|
82
|
+
async execute(ctx: INodeContext, inputs: Record<string, unknown>): Promise<INodeResult> {
|
|
83
|
+
ctx.signal.throwIfAborted();
|
|
84
|
+
const value = inputs['in'];
|
|
85
|
+
if (typeof value !== 'string') {
|
|
86
|
+
// NodeError(code, message, meta?) — a stable code plus a human message.
|
|
87
|
+
throw new NodeError('INVALID_INPUT', 'expected a string on the `in` port');
|
|
88
|
+
}
|
|
89
|
+
return { outputs: { out: value.toUpperCase() }, branch: 'out' };
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
#### Control node — branch outputs
|
|
95
|
+
|
|
96
|
+
A `control` node declares `kind: 'branch'` ports and routes by returning the
|
|
97
|
+
matching `branch`. Condensed from `ConditionIfNode`:
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
export class ConditionIfNode implements INode {
|
|
101
|
+
description = {
|
|
102
|
+
slug: 'revenexx:condition-if',
|
|
103
|
+
version: '1.0.0',
|
|
104
|
+
category: 'control',
|
|
105
|
+
name: { en: 'If condition', de: 'Wenn-Bedingung' },
|
|
106
|
+
icon: 'mdi:source-branch',
|
|
107
|
+
inputs: { in: { dataType: 'any', required: true } },
|
|
108
|
+
outputs: [
|
|
109
|
+
{ name: 'true', kind: 'branch', dataType: 'any', label: { en: 'True', de: 'Wahr' } },
|
|
110
|
+
{ name: 'false', kind: 'branch', dataType: 'any', label: { en: 'False', de: 'Falsch' } },
|
|
111
|
+
],
|
|
112
|
+
config: [
|
|
113
|
+
{ key: 'field', label: { en: 'Field (dot-path)' }, type: 'string', required: true },
|
|
114
|
+
{ key: 'value', label: { en: 'Value' }, type: 'string', expressionAllowed: true },
|
|
115
|
+
],
|
|
116
|
+
} satisfies INodeDescription;
|
|
117
|
+
|
|
118
|
+
async execute(ctx: INodeContext, inputs: Record<string, unknown>): Promise<INodeResult> {
|
|
119
|
+
const inputData = inputs['in'];
|
|
120
|
+
const field = inputs['field'];
|
|
121
|
+
// `field` is a dot-path selector — resolve it against the input payload,
|
|
122
|
+
// then compare the resolved value to the configured `value`.
|
|
123
|
+
const actual =
|
|
124
|
+
typeof field === 'string'
|
|
125
|
+
? field
|
|
126
|
+
.split('.')
|
|
127
|
+
.reduce<unknown>(
|
|
128
|
+
(acc, key) => (acc == null ? undefined : (acc as Record<string, unknown>)[key]),
|
|
129
|
+
inputData,
|
|
130
|
+
)
|
|
131
|
+
: undefined;
|
|
132
|
+
const branch = actual === inputs['value'] ? 'true' : 'false';
|
|
133
|
+
// The fired port's name is both the outputs key and the branch.
|
|
134
|
+
return { outputs: { [branch]: inputData }, branch };
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
#### Action node — error output & credentials
|
|
140
|
+
|
|
141
|
+
An `action` node declares a `kind: 'error'` port for expected, routable
|
|
142
|
+
failures and reads a credential instance via a `credentials-ref` config field.
|
|
143
|
+
Condensed from `HttpRequestNode`:
|
|
144
|
+
|
|
145
|
+
```ts
|
|
146
|
+
export class HttpRequestNode implements INode {
|
|
147
|
+
description = {
|
|
148
|
+
slug: 'revenexx:http-request',
|
|
149
|
+
version: '1.0.0',
|
|
150
|
+
category: 'action',
|
|
151
|
+
name: { en: 'HTTP Request', de: 'HTTP-Anfrage' },
|
|
152
|
+
icon: 'mdi:web',
|
|
153
|
+
inputs: { in: { dataType: 'object' } },
|
|
154
|
+
outputs: [
|
|
155
|
+
{
|
|
156
|
+
name: 'response',
|
|
157
|
+
kind: 'default',
|
|
158
|
+
dataType: 'object',
|
|
159
|
+
label: { en: 'Response', de: 'Antwort' },
|
|
160
|
+
fields: { status: { dataType: 'number' }, body: { dataType: 'any' } },
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
name: 'error',
|
|
164
|
+
kind: 'error',
|
|
165
|
+
dataType: 'object',
|
|
166
|
+
label: { en: 'Error', de: 'Fehler' },
|
|
167
|
+
fields: { code: { dataType: 'string' }, message: { dataType: 'string' } },
|
|
168
|
+
},
|
|
169
|
+
],
|
|
170
|
+
config: [
|
|
171
|
+
{ key: 'url', label: 'URL', type: 'string', required: true, expressionAllowed: true },
|
|
172
|
+
{
|
|
173
|
+
key: 'credentials',
|
|
174
|
+
label: { en: 'Credentials', de: 'Anmeldedaten' },
|
|
175
|
+
type: 'credentials-ref',
|
|
176
|
+
credentialType: 'revenexx:http-bearer',
|
|
177
|
+
},
|
|
178
|
+
],
|
|
179
|
+
} satisfies INodeDescription;
|
|
180
|
+
|
|
181
|
+
async execute(ctx: INodeContext, inputs: Record<string, unknown>): Promise<INodeResult> {
|
|
182
|
+
const url = inputs['url'];
|
|
183
|
+
if (typeof url !== 'string' || !url) {
|
|
184
|
+
throw new NodeError('MISSING_URL', 'URL config field is required');
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const headers: Record<string, string> = { 'Content-Type': 'application/json' };
|
|
188
|
+
const credentialsRef = inputs['credentials'];
|
|
189
|
+
if (typeof credentialsRef === 'string') {
|
|
190
|
+
const creds = await ctx.credentials.get(credentialsRef);
|
|
191
|
+
if (typeof creds['token'] === 'string') headers['Authorization'] = `Bearer ${creds['token']}`;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
try {
|
|
195
|
+
const res = await fetch(url, { headers, signal: ctx.signal }); // always propagate ctx.signal
|
|
196
|
+
const body = await res.json();
|
|
197
|
+
if (!res.ok) {
|
|
198
|
+
return {
|
|
199
|
+
outputs: { error: { code: 'HTTP_ERROR', message: `${url} → ${res.status}` } },
|
|
200
|
+
branch: 'error',
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
return { outputs: { response: { status: res.status, body } }, branch: 'response' };
|
|
204
|
+
} catch (err) {
|
|
205
|
+
if (err instanceof NodeError) throw err;
|
|
206
|
+
// Don't swallow cancellations/timeouts — let aborts propagate so the
|
|
207
|
+
// workflow can cancel promptly instead of routing to the error port.
|
|
208
|
+
if (ctx.signal.aborted || (err instanceof Error && err.name === 'AbortError')) throw err;
|
|
209
|
+
return { outputs: { error: { code: 'REQUEST_FAILED', message: String(err) } }, branch: 'error' };
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
**Error contract:** `throw NodeError(code, message, meta?)` for unexpected,
|
|
216
|
+
system-level failures; `return { outputs, branch: '<error-port>' }` for
|
|
217
|
+
expected, routable errors (a declared `kind: 'error'` port). Never mix both for
|
|
218
|
+
the same condition.
|
|
219
|
+
|
|
220
|
+
### Building a manifest
|
|
221
|
+
|
|
222
|
+
Export a `NODES: INode[]` array from your package entry, then run the bundled
|
|
223
|
+
CLI after your build to emit `dist/manifest.json`:
|
|
224
|
+
|
|
225
|
+
```jsonc
|
|
226
|
+
// package.json
|
|
227
|
+
{
|
|
228
|
+
"scripts": {
|
|
229
|
+
"build": "tsup && rvnxx-nodes manifest"
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
`rvnxx-nodes manifest` imports the package's built `dist/index.js`, reads its
|
|
235
|
+
`NODES` export (plus optional `CREDENTIALS: ICredential[]` and
|
|
236
|
+
`TEMPLATES: ITemplateDescription[]` exports) and writes the manifest envelope.
|
|
237
|
+
There is no `publish` subcommand — node packages are registered through the
|
|
238
|
+
Revenexx Console/Cockpit, not published from the repos.
|
|
239
|
+
|
|
240
|
+
You can also build a manifest programmatically:
|
|
241
|
+
|
|
242
|
+
```ts
|
|
243
|
+
import { buildManifest, extractManifests } from '@revenexx/integrations-node-sdk';
|
|
244
|
+
|
|
245
|
+
const manifest = buildManifest({ nodes: extractManifests(NODES) });
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
## Documentation
|
|
249
|
+
|
|
250
|
+
- [`docs/overview.md`](docs/overview.md) — full API reference & node-authoring guide
|
|
251
|
+
- [`docs/versioning.md`](docs/versioning.md) — SemVer policy & release flow
|
|
252
|
+
|
|
253
|
+
## Development
|
|
254
|
+
|
|
255
|
+
```bash
|
|
256
|
+
npm run build # compile to dist/ (ESM + CJS + .d.ts) via tsup
|
|
257
|
+
npm run dev # tsup watch mode
|
|
258
|
+
npm test # node --test over src/**/*.test.ts
|
|
259
|
+
npm run typecheck # tsc --noEmit
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
## Releasing
|
|
263
|
+
|
|
264
|
+
Versioning and publishing are driven by [Changesets](https://github.com/changesets/changesets)
|
|
265
|
+
and triggered by a git tag — the version is never edited by hand. The publish
|
|
266
|
+
runs in CI against the public npm registry with tokenless OIDC trusted
|
|
267
|
+
publishing. See [`docs/versioning.md`](docs/versioning.md) for the full flow.
|
|
268
|
+
|
|
269
|
+
```bash
|
|
270
|
+
npx changeset # record the intended bump (patch/minor/major)
|
|
271
|
+
# cut a release — main is protected, so the version bump lands via a PR:
|
|
272
|
+
git switch -c release/next
|
|
273
|
+
npx changeset version # bump package.json + CHANGELOG.md
|
|
274
|
+
git add -A && git commit -m "release: version packages" # -A also stages a first-time CHANGELOG.md
|
|
275
|
+
git push -u origin release/next # open a PR → merge into main (CI `test` + 1 approval)
|
|
276
|
+
git switch main && git pull # fast-forward to the merged version commit
|
|
277
|
+
npx changeset tag # tag @revenexx/integrations-node-sdk@X.Y.Z (needs repo admin)
|
|
278
|
+
git push --follow-tags # tag push triggers the publish workflow
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
## License
|
|
282
|
+
|
|
283
|
+
[MIT](LICENSE) © revenexx GmbH
|
package/dist/cli.cjs
CHANGED
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
buildManifest
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-X5HI7WPI.js";
|
|
5
5
|
|
|
6
6
|
// src/cli.ts
|
|
7
7
|
import * as fs from "fs";
|
|
@@ -69,4 +69,3 @@ main().catch((err) => {
|
|
|
69
69
|
console.error(err);
|
|
70
70
|
process.exit(1);
|
|
71
71
|
});
|
|
72
|
-
//# sourceMappingURL=cli.js.map
|
package/dist/index.cjs
CHANGED
|
@@ -23,7 +23,12 @@ __export(index_exports, {
|
|
|
23
23
|
ApiKeyCredential: () => ApiKeyCredential,
|
|
24
24
|
BaseCredential: () => BaseCredential,
|
|
25
25
|
BasicAuthCredential: () => BasicAuthCredential,
|
|
26
|
+
DEFAULT_RETRY_ATTEMPTS: () => DEFAULT_RETRY_ATTEMPTS,
|
|
27
|
+
DEFAULT_RETRY_DELAY_MS: () => DEFAULT_RETRY_DELAY_MS,
|
|
28
|
+
DEFAULT_TIMEOUT_MS: () => DEFAULT_TIMEOUT_MS,
|
|
26
29
|
MANIFEST_VERSION: () => MANIFEST_VERSION,
|
|
30
|
+
MAX_RETRY_ATTEMPTS: () => MAX_RETRY_ATTEMPTS,
|
|
31
|
+
MAX_TIMEOUT_MS: () => MAX_TIMEOUT_MS,
|
|
27
32
|
NodeError: () => NodeError,
|
|
28
33
|
OAuth2AuthCodeCredential: () => OAuth2AuthCodeCredential,
|
|
29
34
|
OAuth2ClientCredentialsCredential: () => OAuth2ClientCredentialsCredential,
|
|
@@ -36,7 +41,10 @@ __export(index_exports, {
|
|
|
36
41
|
isNodeWithIteration: () => isNodeWithIteration,
|
|
37
42
|
isOAuthAuthorizeCredential: () => isOAuthAuthorizeCredential,
|
|
38
43
|
normalizeCredentialType: () => normalizeCredentialType,
|
|
39
|
-
normalizeLocalized: () => normalizeLocalized
|
|
44
|
+
normalizeLocalized: () => normalizeLocalized,
|
|
45
|
+
retryConfigFields: () => retryConfigFields,
|
|
46
|
+
safeFetch: () => safeFetch,
|
|
47
|
+
timeoutConfigField: () => timeoutConfigField
|
|
40
48
|
});
|
|
41
49
|
module.exports = __toCommonJS(index_exports);
|
|
42
50
|
|
|
@@ -82,6 +90,18 @@ function normalizeCredentialType(value) {
|
|
|
82
90
|
return [...seen];
|
|
83
91
|
}
|
|
84
92
|
|
|
93
|
+
// src/errors.ts
|
|
94
|
+
var NodeError = class extends Error {
|
|
95
|
+
code;
|
|
96
|
+
meta;
|
|
97
|
+
constructor(code, message, meta) {
|
|
98
|
+
super(message);
|
|
99
|
+
this.name = "NodeError";
|
|
100
|
+
this.code = code;
|
|
101
|
+
this.meta = meta;
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
|
|
85
105
|
// src/extract.ts
|
|
86
106
|
function extractManifest(node) {
|
|
87
107
|
return node.description;
|
|
@@ -96,6 +116,77 @@ function extractCredentialManifests(credentials) {
|
|
|
96
116
|
return credentials.map(extractCredentialManifest);
|
|
97
117
|
}
|
|
98
118
|
|
|
119
|
+
// src/fetch.ts
|
|
120
|
+
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
121
|
+
var MAX_TIMEOUT_MS = 12e4;
|
|
122
|
+
var DEFAULT_RETRY_ATTEMPTS = 0;
|
|
123
|
+
var MAX_RETRY_ATTEMPTS = 5;
|
|
124
|
+
var DEFAULT_RETRY_DELAY_MS = 1e3;
|
|
125
|
+
async function safeFetch(url, options = {}) {
|
|
126
|
+
const { timeoutMs = DEFAULT_TIMEOUT_MS, signal: ctxSignal, retry, ...fetchOptions } = options;
|
|
127
|
+
const effectiveMs = Number.isFinite(timeoutMs) && timeoutMs > 0 ? Math.min(timeoutMs, MAX_TIMEOUT_MS) : DEFAULT_TIMEOUT_MS;
|
|
128
|
+
const rawAttempts = retry?.attempts ?? DEFAULT_RETRY_ATTEMPTS;
|
|
129
|
+
const safeAttempts = Number.isFinite(rawAttempts) ? Math.min(Math.max(0, rawAttempts), MAX_RETRY_ATTEMPTS) : DEFAULT_RETRY_ATTEMPTS;
|
|
130
|
+
const maxAttempts = safeAttempts + 1;
|
|
131
|
+
const retryDelayMs = retry?.delayMs ?? DEFAULT_RETRY_DELAY_MS;
|
|
132
|
+
let lastError;
|
|
133
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
134
|
+
if (ctxSignal?.aborted) throw ctxSignal.reason;
|
|
135
|
+
const ac = new AbortController();
|
|
136
|
+
const timer = setTimeout(() => ac.abort(), effectiveMs);
|
|
137
|
+
const signal = ctxSignal ? AbortSignal.any([ctxSignal, ac.signal]) : ac.signal;
|
|
138
|
+
try {
|
|
139
|
+
return await fetch(url, { ...fetchOptions, signal });
|
|
140
|
+
} catch (err) {
|
|
141
|
+
if (ctxSignal?.aborted) throw ctxSignal.reason;
|
|
142
|
+
if (ac.signal.aborted) {
|
|
143
|
+
lastError = new NodeError("TIMEOUT", `Request timed out after ${effectiveMs}ms`);
|
|
144
|
+
} else {
|
|
145
|
+
lastError = err;
|
|
146
|
+
}
|
|
147
|
+
if (attempt < maxAttempts) {
|
|
148
|
+
await new Promise((resolve) => {
|
|
149
|
+
const t = setTimeout(resolve, retryDelayMs);
|
|
150
|
+
ctxSignal?.addEventListener("abort", () => {
|
|
151
|
+
clearTimeout(t);
|
|
152
|
+
resolve();
|
|
153
|
+
}, { once: true });
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
} finally {
|
|
157
|
+
clearTimeout(timer);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
throw lastError;
|
|
161
|
+
}
|
|
162
|
+
function timeoutConfigField(opts) {
|
|
163
|
+
return {
|
|
164
|
+
key: "timeoutMs",
|
|
165
|
+
label: "Timeout (ms)",
|
|
166
|
+
type: "number",
|
|
167
|
+
default: opts?.default ?? DEFAULT_TIMEOUT_MS,
|
|
168
|
+
validation: { min: 100, max: opts?.max ?? MAX_TIMEOUT_MS }
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
function retryConfigFields(opts) {
|
|
172
|
+
return [
|
|
173
|
+
{
|
|
174
|
+
key: "retryAttempts",
|
|
175
|
+
label: "Retry attempts",
|
|
176
|
+
type: "number",
|
|
177
|
+
default: opts?.defaultAttempts ?? DEFAULT_RETRY_ATTEMPTS,
|
|
178
|
+
validation: { min: 0, max: MAX_RETRY_ATTEMPTS }
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
key: "retryDelayMs",
|
|
182
|
+
label: "Retry delay (ms)",
|
|
183
|
+
type: "number",
|
|
184
|
+
default: opts?.defaultDelayMs ?? DEFAULT_RETRY_DELAY_MS,
|
|
185
|
+
validation: { min: 100 }
|
|
186
|
+
}
|
|
187
|
+
];
|
|
188
|
+
}
|
|
189
|
+
|
|
99
190
|
// src/manifest.ts
|
|
100
191
|
var MANIFEST_VERSION = "v0-draft";
|
|
101
192
|
function buildManifest(nodes, credentials = [], templates = []) {
|
|
@@ -316,24 +407,17 @@ var OAuth2AuthCodeCredential = class extends BaseCredential {
|
|
|
316
407
|
function base64Url(buf) {
|
|
317
408
|
return buf.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
318
409
|
}
|
|
319
|
-
|
|
320
|
-
// src/errors.ts
|
|
321
|
-
var NodeError = class extends Error {
|
|
322
|
-
code;
|
|
323
|
-
meta;
|
|
324
|
-
constructor(code, message, meta) {
|
|
325
|
-
super(message);
|
|
326
|
-
this.name = "NodeError";
|
|
327
|
-
this.code = code;
|
|
328
|
-
this.meta = meta;
|
|
329
|
-
}
|
|
330
|
-
};
|
|
331
410
|
// Annotate the CommonJS export names for ESM import in node:
|
|
332
411
|
0 && (module.exports = {
|
|
333
412
|
ApiKeyCredential,
|
|
334
413
|
BaseCredential,
|
|
335
414
|
BasicAuthCredential,
|
|
415
|
+
DEFAULT_RETRY_ATTEMPTS,
|
|
416
|
+
DEFAULT_RETRY_DELAY_MS,
|
|
417
|
+
DEFAULT_TIMEOUT_MS,
|
|
336
418
|
MANIFEST_VERSION,
|
|
419
|
+
MAX_RETRY_ATTEMPTS,
|
|
420
|
+
MAX_TIMEOUT_MS,
|
|
337
421
|
NodeError,
|
|
338
422
|
OAuth2AuthCodeCredential,
|
|
339
423
|
OAuth2ClientCredentialsCredential,
|
|
@@ -346,6 +430,8 @@ var NodeError = class extends Error {
|
|
|
346
430
|
isNodeWithIteration,
|
|
347
431
|
isOAuthAuthorizeCredential,
|
|
348
432
|
normalizeCredentialType,
|
|
349
|
-
normalizeLocalized
|
|
433
|
+
normalizeLocalized,
|
|
434
|
+
retryConfigFields,
|
|
435
|
+
safeFetch,
|
|
436
|
+
timeoutConfigField
|
|
350
437
|
});
|
|
351
|
-
//# sourceMappingURL=index.cjs.map
|
package/dist/index.d.cts
CHANGED
|
@@ -314,11 +314,42 @@ declare function normalizeLocalized(value: LocalizedString | undefined | null, f
|
|
|
314
314
|
*/
|
|
315
315
|
declare function normalizeCredentialType(value: string | string[] | undefined): string[];
|
|
316
316
|
|
|
317
|
+
declare class NodeError extends Error {
|
|
318
|
+
readonly code: string;
|
|
319
|
+
readonly meta?: Record<string, unknown>;
|
|
320
|
+
constructor(code: string, message: string, meta?: Record<string, unknown>);
|
|
321
|
+
}
|
|
322
|
+
|
|
317
323
|
declare function extractManifest(node: INode): INodeDescription;
|
|
318
324
|
declare function extractManifests(nodes: INode[]): INodeDescription[];
|
|
319
325
|
declare function extractCredentialManifest(credential: ICredential): ICredentialDescription;
|
|
320
326
|
declare function extractCredentialManifests(credentials: ICredential[]): ICredentialDescription[];
|
|
321
327
|
|
|
328
|
+
declare const DEFAULT_TIMEOUT_MS = 30000;
|
|
329
|
+
declare const MAX_TIMEOUT_MS = 120000;
|
|
330
|
+
declare const DEFAULT_RETRY_ATTEMPTS = 0;
|
|
331
|
+
declare const MAX_RETRY_ATTEMPTS = 5;
|
|
332
|
+
declare const DEFAULT_RETRY_DELAY_MS = 1000;
|
|
333
|
+
interface SafeFetchRetry {
|
|
334
|
+
attempts: number;
|
|
335
|
+
delayMs?: number;
|
|
336
|
+
}
|
|
337
|
+
interface SafeFetchOptions extends RequestInit {
|
|
338
|
+
timeoutMs?: number;
|
|
339
|
+
/** Pass `ctx.signal` so the workflow engine can cancel the request. */
|
|
340
|
+
signal?: AbortSignal;
|
|
341
|
+
retry?: SafeFetchRetry;
|
|
342
|
+
}
|
|
343
|
+
declare function safeFetch(url: string | URL, options?: SafeFetchOptions): Promise<Response>;
|
|
344
|
+
declare function timeoutConfigField(opts?: {
|
|
345
|
+
default?: number;
|
|
346
|
+
max?: number;
|
|
347
|
+
}): IConfigField;
|
|
348
|
+
declare function retryConfigFields(opts?: {
|
|
349
|
+
defaultAttempts?: number;
|
|
350
|
+
defaultDelayMs?: number;
|
|
351
|
+
}): IConfigField[];
|
|
352
|
+
|
|
322
353
|
/**
|
|
323
354
|
* Envelope version expected by the integrations server-side
|
|
324
355
|
* `TarballInspector`. Earlier (pre-registry) builds emitted a bare array
|
|
@@ -458,10 +489,4 @@ declare abstract class OAuth2AuthCodeCredential extends BaseCredential implement
|
|
|
458
489
|
test(_ctx: ICredentialContext, config: Config): Promise<ICredentialTestResult>;
|
|
459
490
|
}
|
|
460
491
|
|
|
461
|
-
|
|
462
|
-
readonly code: string;
|
|
463
|
-
readonly meta?: Record<string, unknown>;
|
|
464
|
-
constructor(code: string, message: string, meta?: Record<string, unknown>);
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
export { ApiKeyCredential, BaseCredential, BasicAuthCredential, type ConfigType, type CredentialAuthKind, type CredentialFieldType, 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 IInputPort, type INode, type INodeContext, type INodeDescription, type INodeResult, type INodeWithIteration, type IOutputField, type IOutputPort, type ITemplateDescription, type ITemplateTrigger, type LocalizedString, MANIFEST_VERSION, type NodeCategory, NodeError, type NodeManifest, OAuth2AuthCodeCredential, OAuth2ClientCredentialsCredential, type OAuthTokenResponse, type OutputKind, SimpleValueCredential, type TemplateLevel, type TemplateTriggerType, buildManifest, extractCredentialManifest, extractCredentialManifests, extractManifest, extractManifests, isNodeWithIteration, isOAuthAuthorizeCredential, normalizeCredentialType, normalizeLocalized };
|
|
492
|
+
export { ApiKeyCredential, BaseCredential, BasicAuthCredential, type ConfigType, type CredentialAuthKind, type CredentialFieldType, DEFAULT_RETRY_ATTEMPTS, DEFAULT_RETRY_DELAY_MS, 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 IInputPort, type INode, type INodeContext, type INodeDescription, type INodeResult, type INodeWithIteration, type IOutputField, type IOutputPort, type ITemplateDescription, type ITemplateTrigger, type LocalizedString, MANIFEST_VERSION, MAX_RETRY_ATTEMPTS, MAX_TIMEOUT_MS, type NodeCategory, NodeError, type NodeManifest, OAuth2AuthCodeCredential, OAuth2ClientCredentialsCredential, type OAuthTokenResponse, type OutputKind, type SafeFetchOptions, type SafeFetchRetry, SimpleValueCredential, type TemplateLevel, type TemplateTriggerType, buildManifest, extractCredentialManifest, extractCredentialManifests, extractManifest, extractManifests, isNodeWithIteration, isOAuthAuthorizeCredential, normalizeCredentialType, normalizeLocalized, retryConfigFields, safeFetch, timeoutConfigField };
|
package/dist/index.d.ts
CHANGED
|
@@ -314,11 +314,42 @@ declare function normalizeLocalized(value: LocalizedString | undefined | null, f
|
|
|
314
314
|
*/
|
|
315
315
|
declare function normalizeCredentialType(value: string | string[] | undefined): string[];
|
|
316
316
|
|
|
317
|
+
declare class NodeError extends Error {
|
|
318
|
+
readonly code: string;
|
|
319
|
+
readonly meta?: Record<string, unknown>;
|
|
320
|
+
constructor(code: string, message: string, meta?: Record<string, unknown>);
|
|
321
|
+
}
|
|
322
|
+
|
|
317
323
|
declare function extractManifest(node: INode): INodeDescription;
|
|
318
324
|
declare function extractManifests(nodes: INode[]): INodeDescription[];
|
|
319
325
|
declare function extractCredentialManifest(credential: ICredential): ICredentialDescription;
|
|
320
326
|
declare function extractCredentialManifests(credentials: ICredential[]): ICredentialDescription[];
|
|
321
327
|
|
|
328
|
+
declare const DEFAULT_TIMEOUT_MS = 30000;
|
|
329
|
+
declare const MAX_TIMEOUT_MS = 120000;
|
|
330
|
+
declare const DEFAULT_RETRY_ATTEMPTS = 0;
|
|
331
|
+
declare const MAX_RETRY_ATTEMPTS = 5;
|
|
332
|
+
declare const DEFAULT_RETRY_DELAY_MS = 1000;
|
|
333
|
+
interface SafeFetchRetry {
|
|
334
|
+
attempts: number;
|
|
335
|
+
delayMs?: number;
|
|
336
|
+
}
|
|
337
|
+
interface SafeFetchOptions extends RequestInit {
|
|
338
|
+
timeoutMs?: number;
|
|
339
|
+
/** Pass `ctx.signal` so the workflow engine can cancel the request. */
|
|
340
|
+
signal?: AbortSignal;
|
|
341
|
+
retry?: SafeFetchRetry;
|
|
342
|
+
}
|
|
343
|
+
declare function safeFetch(url: string | URL, options?: SafeFetchOptions): Promise<Response>;
|
|
344
|
+
declare function timeoutConfigField(opts?: {
|
|
345
|
+
default?: number;
|
|
346
|
+
max?: number;
|
|
347
|
+
}): IConfigField;
|
|
348
|
+
declare function retryConfigFields(opts?: {
|
|
349
|
+
defaultAttempts?: number;
|
|
350
|
+
defaultDelayMs?: number;
|
|
351
|
+
}): IConfigField[];
|
|
352
|
+
|
|
322
353
|
/**
|
|
323
354
|
* Envelope version expected by the integrations server-side
|
|
324
355
|
* `TarballInspector`. Earlier (pre-registry) builds emitted a bare array
|
|
@@ -458,10 +489,4 @@ declare abstract class OAuth2AuthCodeCredential extends BaseCredential implement
|
|
|
458
489
|
test(_ctx: ICredentialContext, config: Config): Promise<ICredentialTestResult>;
|
|
459
490
|
}
|
|
460
491
|
|
|
461
|
-
|
|
462
|
-
readonly code: string;
|
|
463
|
-
readonly meta?: Record<string, unknown>;
|
|
464
|
-
constructor(code: string, message: string, meta?: Record<string, unknown>);
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
export { ApiKeyCredential, BaseCredential, BasicAuthCredential, type ConfigType, type CredentialAuthKind, type CredentialFieldType, 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 IInputPort, type INode, type INodeContext, type INodeDescription, type INodeResult, type INodeWithIteration, type IOutputField, type IOutputPort, type ITemplateDescription, type ITemplateTrigger, type LocalizedString, MANIFEST_VERSION, type NodeCategory, NodeError, type NodeManifest, OAuth2AuthCodeCredential, OAuth2ClientCredentialsCredential, type OAuthTokenResponse, type OutputKind, SimpleValueCredential, type TemplateLevel, type TemplateTriggerType, buildManifest, extractCredentialManifest, extractCredentialManifests, extractManifest, extractManifests, isNodeWithIteration, isOAuthAuthorizeCredential, normalizeCredentialType, normalizeLocalized };
|
|
492
|
+
export { ApiKeyCredential, BaseCredential, BasicAuthCredential, type ConfigType, type CredentialAuthKind, type CredentialFieldType, DEFAULT_RETRY_ATTEMPTS, DEFAULT_RETRY_DELAY_MS, 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 IInputPort, type INode, type INodeContext, type INodeDescription, type INodeResult, type INodeWithIteration, type IOutputField, type IOutputPort, type ITemplateDescription, type ITemplateTrigger, type LocalizedString, MANIFEST_VERSION, MAX_RETRY_ATTEMPTS, MAX_TIMEOUT_MS, type NodeCategory, NodeError, type NodeManifest, OAuth2AuthCodeCredential, OAuth2ClientCredentialsCredential, type OAuthTokenResponse, type OutputKind, type SafeFetchOptions, type SafeFetchRetry, SimpleValueCredential, type TemplateLevel, type TemplateTriggerType, buildManifest, extractCredentialManifest, extractCredentialManifests, extractManifest, extractManifests, isNodeWithIteration, isOAuthAuthorizeCredential, normalizeCredentialType, normalizeLocalized, retryConfigFields, safeFetch, timeoutConfigField };
|
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
extractCredentialManifests,
|
|
6
6
|
extractManifest,
|
|
7
7
|
extractManifests
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-X5HI7WPI.js";
|
|
9
9
|
|
|
10
10
|
// src/types.ts
|
|
11
11
|
function isNodeWithIteration(node) {
|
|
@@ -49,6 +49,89 @@ function normalizeCredentialType(value) {
|
|
|
49
49
|
return [...seen];
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
// src/errors.ts
|
|
53
|
+
var NodeError = class extends Error {
|
|
54
|
+
code;
|
|
55
|
+
meta;
|
|
56
|
+
constructor(code, message, meta) {
|
|
57
|
+
super(message);
|
|
58
|
+
this.name = "NodeError";
|
|
59
|
+
this.code = code;
|
|
60
|
+
this.meta = meta;
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
// src/fetch.ts
|
|
65
|
+
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
66
|
+
var MAX_TIMEOUT_MS = 12e4;
|
|
67
|
+
var DEFAULT_RETRY_ATTEMPTS = 0;
|
|
68
|
+
var MAX_RETRY_ATTEMPTS = 5;
|
|
69
|
+
var DEFAULT_RETRY_DELAY_MS = 1e3;
|
|
70
|
+
async function safeFetch(url, options = {}) {
|
|
71
|
+
const { timeoutMs = DEFAULT_TIMEOUT_MS, signal: ctxSignal, retry, ...fetchOptions } = options;
|
|
72
|
+
const effectiveMs = Number.isFinite(timeoutMs) && timeoutMs > 0 ? Math.min(timeoutMs, MAX_TIMEOUT_MS) : DEFAULT_TIMEOUT_MS;
|
|
73
|
+
const rawAttempts = retry?.attempts ?? DEFAULT_RETRY_ATTEMPTS;
|
|
74
|
+
const safeAttempts = Number.isFinite(rawAttempts) ? Math.min(Math.max(0, rawAttempts), MAX_RETRY_ATTEMPTS) : DEFAULT_RETRY_ATTEMPTS;
|
|
75
|
+
const maxAttempts = safeAttempts + 1;
|
|
76
|
+
const retryDelayMs = retry?.delayMs ?? DEFAULT_RETRY_DELAY_MS;
|
|
77
|
+
let lastError;
|
|
78
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
79
|
+
if (ctxSignal?.aborted) throw ctxSignal.reason;
|
|
80
|
+
const ac = new AbortController();
|
|
81
|
+
const timer = setTimeout(() => ac.abort(), effectiveMs);
|
|
82
|
+
const signal = ctxSignal ? AbortSignal.any([ctxSignal, ac.signal]) : ac.signal;
|
|
83
|
+
try {
|
|
84
|
+
return await fetch(url, { ...fetchOptions, signal });
|
|
85
|
+
} catch (err) {
|
|
86
|
+
if (ctxSignal?.aborted) throw ctxSignal.reason;
|
|
87
|
+
if (ac.signal.aborted) {
|
|
88
|
+
lastError = new NodeError("TIMEOUT", `Request timed out after ${effectiveMs}ms`);
|
|
89
|
+
} else {
|
|
90
|
+
lastError = err;
|
|
91
|
+
}
|
|
92
|
+
if (attempt < maxAttempts) {
|
|
93
|
+
await new Promise((resolve) => {
|
|
94
|
+
const t = setTimeout(resolve, retryDelayMs);
|
|
95
|
+
ctxSignal?.addEventListener("abort", () => {
|
|
96
|
+
clearTimeout(t);
|
|
97
|
+
resolve();
|
|
98
|
+
}, { once: true });
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
} finally {
|
|
102
|
+
clearTimeout(timer);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
throw lastError;
|
|
106
|
+
}
|
|
107
|
+
function timeoutConfigField(opts) {
|
|
108
|
+
return {
|
|
109
|
+
key: "timeoutMs",
|
|
110
|
+
label: "Timeout (ms)",
|
|
111
|
+
type: "number",
|
|
112
|
+
default: opts?.default ?? DEFAULT_TIMEOUT_MS,
|
|
113
|
+
validation: { min: 100, max: opts?.max ?? MAX_TIMEOUT_MS }
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
function retryConfigFields(opts) {
|
|
117
|
+
return [
|
|
118
|
+
{
|
|
119
|
+
key: "retryAttempts",
|
|
120
|
+
label: "Retry attempts",
|
|
121
|
+
type: "number",
|
|
122
|
+
default: opts?.defaultAttempts ?? DEFAULT_RETRY_ATTEMPTS,
|
|
123
|
+
validation: { min: 0, max: MAX_RETRY_ATTEMPTS }
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
key: "retryDelayMs",
|
|
127
|
+
label: "Retry delay (ms)",
|
|
128
|
+
type: "number",
|
|
129
|
+
default: opts?.defaultDelayMs ?? DEFAULT_RETRY_DELAY_MS,
|
|
130
|
+
validation: { min: 100 }
|
|
131
|
+
}
|
|
132
|
+
];
|
|
133
|
+
}
|
|
134
|
+
|
|
52
135
|
// src/credentials.ts
|
|
53
136
|
import { createHash, randomBytes } from "crypto";
|
|
54
137
|
function readString(source, key) {
|
|
@@ -253,23 +336,16 @@ var OAuth2AuthCodeCredential = class extends BaseCredential {
|
|
|
253
336
|
function base64Url(buf) {
|
|
254
337
|
return buf.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
255
338
|
}
|
|
256
|
-
|
|
257
|
-
// src/errors.ts
|
|
258
|
-
var NodeError = class extends Error {
|
|
259
|
-
code;
|
|
260
|
-
meta;
|
|
261
|
-
constructor(code, message, meta) {
|
|
262
|
-
super(message);
|
|
263
|
-
this.name = "NodeError";
|
|
264
|
-
this.code = code;
|
|
265
|
-
this.meta = meta;
|
|
266
|
-
}
|
|
267
|
-
};
|
|
268
339
|
export {
|
|
269
340
|
ApiKeyCredential,
|
|
270
341
|
BaseCredential,
|
|
271
342
|
BasicAuthCredential,
|
|
343
|
+
DEFAULT_RETRY_ATTEMPTS,
|
|
344
|
+
DEFAULT_RETRY_DELAY_MS,
|
|
345
|
+
DEFAULT_TIMEOUT_MS,
|
|
272
346
|
MANIFEST_VERSION,
|
|
347
|
+
MAX_RETRY_ATTEMPTS,
|
|
348
|
+
MAX_TIMEOUT_MS,
|
|
273
349
|
NodeError,
|
|
274
350
|
OAuth2AuthCodeCredential,
|
|
275
351
|
OAuth2ClientCredentialsCredential,
|
|
@@ -282,6 +358,8 @@ export {
|
|
|
282
358
|
isNodeWithIteration,
|
|
283
359
|
isOAuthAuthorizeCredential,
|
|
284
360
|
normalizeCredentialType,
|
|
285
|
-
normalizeLocalized
|
|
361
|
+
normalizeLocalized,
|
|
362
|
+
retryConfigFields,
|
|
363
|
+
safeFetch,
|
|
364
|
+
timeoutConfigField
|
|
286
365
|
};
|
|
287
|
-
//# sourceMappingURL=index.js.map
|
package/package.json
CHANGED
|
@@ -1,8 +1,26 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@revenexx/integrations-node-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.1",
|
|
4
4
|
"description": "TypeScript interfaces and utilities for Revenexx integration nodes",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "revenexx GmbH",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"revenexx",
|
|
9
|
+
"integrations",
|
|
10
|
+
"nodes",
|
|
11
|
+
"sdk"
|
|
12
|
+
],
|
|
5
13
|
"type": "module",
|
|
14
|
+
"sideEffects": false,
|
|
15
|
+
"homepage": "https://revenexx.com",
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/revenexx-integrations/sdk.git"
|
|
19
|
+
},
|
|
20
|
+
"bugs": {
|
|
21
|
+
"url": "https://github.com/revenexx-integrations/sdk/issues",
|
|
22
|
+
"email": "support@revenexx.com"
|
|
23
|
+
},
|
|
6
24
|
"main": "./dist/index.cjs",
|
|
7
25
|
"module": "./dist/index.js",
|
|
8
26
|
"types": "./dist/index.d.ts",
|
|
@@ -16,16 +34,27 @@
|
|
|
16
34
|
"require": "./dist/index.cjs"
|
|
17
35
|
}
|
|
18
36
|
},
|
|
19
|
-
"files": [
|
|
37
|
+
"files": [
|
|
38
|
+
"dist"
|
|
39
|
+
],
|
|
40
|
+
"publishConfig": {
|
|
41
|
+
"access": "public",
|
|
42
|
+
"provenance": true
|
|
43
|
+
},
|
|
20
44
|
"scripts": {
|
|
21
45
|
"build": "tsup",
|
|
22
46
|
"dev": "tsup --watch",
|
|
23
47
|
"test": "node --import tsx --test \"src/**/*.test.ts\"",
|
|
24
48
|
"typecheck": "tsc --noEmit",
|
|
25
|
-
"prepublishOnly": "npm run build"
|
|
49
|
+
"prepublishOnly": "npm run build",
|
|
50
|
+
"release": "changeset publish"
|
|
51
|
+
},
|
|
52
|
+
"engines": {
|
|
53
|
+
"node": ">=20.3.0"
|
|
26
54
|
},
|
|
27
55
|
"devDependencies": {
|
|
28
|
-
"@
|
|
56
|
+
"@changesets/cli": "^2.31.0",
|
|
57
|
+
"@types/node": "^24.0.0",
|
|
29
58
|
"tsup": "^8.0.0",
|
|
30
59
|
"tsx": "^4.0.0",
|
|
31
60
|
"typescript": "^5.0.0"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/extract.ts","../src/manifest.ts"],"sourcesContent":["import type { ICredential, ICredentialDescription, INode, INodeDescription } from './types.js';\n\nexport function extractManifest(node: INode): INodeDescription {\n return node.description;\n}\n\nexport function extractManifests(nodes: INode[]): INodeDescription[] {\n return nodes.map(extractManifest);\n}\n\nexport function extractCredentialManifest(credential: ICredential): ICredentialDescription {\n return credential.description;\n}\n\nexport function extractCredentialManifests(credentials: ICredential[]): ICredentialDescription[] {\n return credentials.map(extractCredentialManifest);\n}\n","import { extractCredentialManifests, extractManifests } from './extract.js';\nimport type {\n ICredential,\n ICredentialDescription,\n INode,\n INodeDescription,\n ITemplateDescription,\n} from './types.js';\n\n/**\n * Envelope version expected by the integrations server-side\n * `TarballInspector`. Earlier (pre-registry) builds emitted a bare array\n * without any `manifestVersion` field; `v0-draft` is currently the only\n * accepted value (see `SchemaServiceProvider::DOMAIN_NODE` in the\n * integrations service). Keeping it versioned lets the registry evolve the\n * shape without breaking older publishers.\n */\nexport const MANIFEST_VERSION = 'v0-draft';\n\nexport interface NodeManifest {\n manifestVersion: typeof MANIFEST_VERSION;\n nodes: INodeDescription[];\n /**\n * Credential types this package publishes. Optional and additive: packages\n * that ship no credentials omit it (older publishers never set it).\n */\n credentials?: ICredentialDescription[];\n /**\n * Workflow templates this package publishes. Optional and additive: packages\n * that ship no templates omit it. Templates are plain data (no executable\n * code), so they are carried verbatim from the package's `TEMPLATES` export.\n */\n templates?: ITemplateDescription[];\n}\n\n/**\n * Builds the manifest envelope from a package's `NODES` (and optional\n * `CREDENTIALS` / `TEMPLATES`) exports. The result is what gets written to\n * `dist/manifest.json` and uploaded to the registry inside the tarball.\n */\nexport function buildManifest(\n nodes: INode[],\n credentials: ICredential[] = [],\n templates: ITemplateDescription[] = [],\n): NodeManifest {\n const manifest: NodeManifest = {\n manifestVersion: MANIFEST_VERSION,\n nodes: extractManifests(nodes),\n };\n if (credentials.length > 0) {\n manifest.credentials = extractCredentialManifests(credentials);\n }\n if (templates.length > 0) {\n manifest.templates = templates;\n }\n return manifest;\n}\n"],"mappings":";AAEO,SAAS,gBAAgB,MAA+B;AAC7D,SAAO,KAAK;AACd;AAEO,SAAS,iBAAiB,OAAoC;AACnE,SAAO,MAAM,IAAI,eAAe;AAClC;AAEO,SAAS,0BAA0B,YAAiD;AACzF,SAAO,WAAW;AACpB;AAEO,SAAS,2BAA2B,aAAsD;AAC/F,SAAO,YAAY,IAAI,yBAAyB;AAClD;;;ACCO,IAAM,mBAAmB;AAuBzB,SAAS,cACd,OACA,cAA6B,CAAC,GAC9B,YAAoC,CAAC,GACvB;AACd,QAAM,WAAyB;AAAA,IAC7B,iBAAiB;AAAA,IACjB,OAAO,iBAAiB,KAAK;AAAA,EAC/B;AACA,MAAI,YAAY,SAAS,GAAG;AAC1B,aAAS,cAAc,2BAA2B,WAAW;AAAA,EAC/D;AACA,MAAI,UAAU,SAAS,GAAG;AACxB,aAAS,YAAY;AAAA,EACvB;AACA,SAAO;AACT;","names":[]}
|
package/dist/cli.cjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli.ts","../src/extract.ts","../src/manifest.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * `rvnxx-nodes` — shared tooling for Revenexx node packages.\n *\n * Subcommands:\n * rvnxx-nodes manifest Read the package's built `NODES` export and write\n * `dist/manifest.json`. Run after `tsup`.\n *\n * Everything operates on the current working directory (`process.cwd()`),\n * so the same binary works from any node package that depends on the SDK.\n *\n * Node packages are NOT published from the repos themselves — registration\n * happens through the Revenexx Console/Cockpit (and, for local development,\n * via `integrations/scripts/update-dev.sh`, which uploads the packed tarball\n * to the admin API). Hence there is no `publish` subcommand here.\n */\n\nimport * as fs from 'node:fs';\nimport { resolve } from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport { buildManifest } from './manifest.js';\nimport type { ICredential, INode, ITemplateDescription } from './types.js';\n\nconst projectRoot = process.cwd();\n\nfunction fail(message: string): never {\n console.error(`Error: ${message}`);\n process.exit(1);\n}\n\n// --------------------------------------------------------------- manifest\n\nasync function runManifest(): Promise<void> {\n const distEntry = resolve(projectRoot, 'dist', 'index.js');\n if (!fs.existsSync(distEntry)) {\n fail('dist/index.js is missing. Run the build (tsup) before `rvnxx-nodes manifest`.');\n }\n\n const mod = (await import(pathToFileURL(distEntry).href)) as {\n NODES?: unknown;\n CREDENTIALS?: unknown;\n TEMPLATES?: unknown;\n };\n if (!Array.isArray(mod.NODES)) {\n fail('dist/index.js does not export a `NODES` array. Export `NODES: INode[]` from your package entry.');\n }\n if (mod.CREDENTIALS !== undefined && !Array.isArray(mod.CREDENTIALS)) {\n fail('dist/index.js exports `CREDENTIALS` but it is not an array. Export `CREDENTIALS: ICredential[]`.');\n }\n if (mod.TEMPLATES !== undefined && !Array.isArray(mod.TEMPLATES)) {\n fail('dist/index.js exports `TEMPLATES` but it is not an array. Export `TEMPLATES: ITemplateDescription[]`.');\n }\n\n const credentials = (mod.CREDENTIALS as ICredential[] | undefined) ?? [];\n const templates = (mod.TEMPLATES as ITemplateDescription[] | undefined) ?? [];\n const manifest = buildManifest(mod.NODES as INode[], credentials, templates);\n\n const outDir = resolve(projectRoot, 'dist');\n const outFile = resolve(outDir, 'manifest.json');\n fs.mkdirSync(outDir, { recursive: true });\n fs.writeFileSync(outFile, JSON.stringify(manifest, null, 2), 'utf-8');\n\n const credentialCount = manifest.credentials?.length ?? 0;\n const templateCount = manifest.templates?.length ?? 0;\n console.log(\n `✓ dist/manifest.json — ${manifest.nodes.length} node(s), ${credentialCount} credential(s), ${templateCount} template(s)`,\n );\n for (const m of manifest.nodes) {\n console.log(` node ${m.slug}@${m.version}`);\n }\n for (const c of manifest.credentials ?? []) {\n console.log(` credential ${c.slug}@${c.version} (${c.authKind})`);\n }\n for (const t of manifest.templates ?? []) {\n console.log(` template ${t.slug}@${t.version} (${t.level})`);\n }\n}\n\n// ------------------------------------------------------------------- main\n\nasync function main(): Promise<void> {\n const command = process.argv[2];\n switch (command) {\n case 'manifest':\n await runManifest();\n break;\n case 'publish':\n console.error(\n 'The `publish` command has been removed. Node packages are no longer ' +\n 'published from the repos themselves — registration happens through the ' +\n 'Revenexx Console/Cockpit. For local development, use ' +\n '`integrations/scripts/register-nodes-core.sh`, which packs and uploads ' +\n 'the tarball to the admin API.',\n );\n process.exit(1);\n default:\n console.error('Usage: rvnxx-nodes <manifest>');\n process.exit(1);\n }\n}\n\nmain().catch((err) => {\n console.error(err);\n process.exit(1);\n});\n","import type { ICredential, ICredentialDescription, INode, INodeDescription } from './types.js';\n\nexport function extractManifest(node: INode): INodeDescription {\n return node.description;\n}\n\nexport function extractManifests(nodes: INode[]): INodeDescription[] {\n return nodes.map(extractManifest);\n}\n\nexport function extractCredentialManifest(credential: ICredential): ICredentialDescription {\n return credential.description;\n}\n\nexport function extractCredentialManifests(credentials: ICredential[]): ICredentialDescription[] {\n return credentials.map(extractCredentialManifest);\n}\n","import { extractCredentialManifests, extractManifests } from './extract.js';\nimport type {\n ICredential,\n ICredentialDescription,\n INode,\n INodeDescription,\n ITemplateDescription,\n} from './types.js';\n\n/**\n * Envelope version expected by the integrations server-side\n * `TarballInspector`. Earlier (pre-registry) builds emitted a bare array\n * without any `manifestVersion` field; `v0-draft` is currently the only\n * accepted value (see `SchemaServiceProvider::DOMAIN_NODE` in the\n * integrations service). Keeping it versioned lets the registry evolve the\n * shape without breaking older publishers.\n */\nexport const MANIFEST_VERSION = 'v0-draft';\n\nexport interface NodeManifest {\n manifestVersion: typeof MANIFEST_VERSION;\n nodes: INodeDescription[];\n /**\n * Credential types this package publishes. Optional and additive: packages\n * that ship no credentials omit it (older publishers never set it).\n */\n credentials?: ICredentialDescription[];\n /**\n * Workflow templates this package publishes. Optional and additive: packages\n * that ship no templates omit it. Templates are plain data (no executable\n * code), so they are carried verbatim from the package's `TEMPLATES` export.\n */\n templates?: ITemplateDescription[];\n}\n\n/**\n * Builds the manifest envelope from a package's `NODES` (and optional\n * `CREDENTIALS` / `TEMPLATES`) exports. The result is what gets written to\n * `dist/manifest.json` and uploaded to the registry inside the tarball.\n */\nexport function buildManifest(\n nodes: INode[],\n credentials: ICredential[] = [],\n templates: ITemplateDescription[] = [],\n): NodeManifest {\n const manifest: NodeManifest = {\n manifestVersion: MANIFEST_VERSION,\n nodes: extractManifests(nodes),\n };\n if (credentials.length > 0) {\n manifest.credentials = extractCredentialManifests(credentials);\n }\n if (templates.length > 0) {\n manifest.templates = templates;\n }\n return manifest;\n}\n"],"mappings":"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","names":[]}
|
package/dist/cli.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * `rvnxx-nodes` — shared tooling for Revenexx node packages.\n *\n * Subcommands:\n * rvnxx-nodes manifest Read the package's built `NODES` export and write\n * `dist/manifest.json`. Run after `tsup`.\n *\n * Everything operates on the current working directory (`process.cwd()`),\n * so the same binary works from any node package that depends on the SDK.\n *\n * Node packages are NOT published from the repos themselves — registration\n * happens through the Revenexx Console/Cockpit (and, for local development,\n * via `integrations/scripts/update-dev.sh`, which uploads the packed tarball\n * to the admin API). Hence there is no `publish` subcommand here.\n */\n\nimport * as fs from 'node:fs';\nimport { resolve } from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport { buildManifest } from './manifest.js';\nimport type { ICredential, INode, ITemplateDescription } from './types.js';\n\nconst projectRoot = process.cwd();\n\nfunction fail(message: string): never {\n console.error(`Error: ${message}`);\n process.exit(1);\n}\n\n// --------------------------------------------------------------- manifest\n\nasync function runManifest(): Promise<void> {\n const distEntry = resolve(projectRoot, 'dist', 'index.js');\n if (!fs.existsSync(distEntry)) {\n fail('dist/index.js is missing. Run the build (tsup) before `rvnxx-nodes manifest`.');\n }\n\n const mod = (await import(pathToFileURL(distEntry).href)) as {\n NODES?: unknown;\n CREDENTIALS?: unknown;\n TEMPLATES?: unknown;\n };\n if (!Array.isArray(mod.NODES)) {\n fail('dist/index.js does not export a `NODES` array. Export `NODES: INode[]` from your package entry.');\n }\n if (mod.CREDENTIALS !== undefined && !Array.isArray(mod.CREDENTIALS)) {\n fail('dist/index.js exports `CREDENTIALS` but it is not an array. Export `CREDENTIALS: ICredential[]`.');\n }\n if (mod.TEMPLATES !== undefined && !Array.isArray(mod.TEMPLATES)) {\n fail('dist/index.js exports `TEMPLATES` but it is not an array. Export `TEMPLATES: ITemplateDescription[]`.');\n }\n\n const credentials = (mod.CREDENTIALS as ICredential[] | undefined) ?? [];\n const templates = (mod.TEMPLATES as ITemplateDescription[] | undefined) ?? [];\n const manifest = buildManifest(mod.NODES as INode[], credentials, templates);\n\n const outDir = resolve(projectRoot, 'dist');\n const outFile = resolve(outDir, 'manifest.json');\n fs.mkdirSync(outDir, { recursive: true });\n fs.writeFileSync(outFile, JSON.stringify(manifest, null, 2), 'utf-8');\n\n const credentialCount = manifest.credentials?.length ?? 0;\n const templateCount = manifest.templates?.length ?? 0;\n console.log(\n `✓ dist/manifest.json — ${manifest.nodes.length} node(s), ${credentialCount} credential(s), ${templateCount} template(s)`,\n );\n for (const m of manifest.nodes) {\n console.log(` node ${m.slug}@${m.version}`);\n }\n for (const c of manifest.credentials ?? []) {\n console.log(` credential ${c.slug}@${c.version} (${c.authKind})`);\n }\n for (const t of manifest.templates ?? []) {\n console.log(` template ${t.slug}@${t.version} (${t.level})`);\n }\n}\n\n// ------------------------------------------------------------------- main\n\nasync function main(): Promise<void> {\n const command = process.argv[2];\n switch (command) {\n case 'manifest':\n await runManifest();\n break;\n case 'publish':\n console.error(\n 'The `publish` command has been removed. Node packages are no longer ' +\n 'published from the repos themselves — registration happens through the ' +\n 'Revenexx Console/Cockpit. For local development, use ' +\n '`integrations/scripts/register-nodes-core.sh`, which packs and uploads ' +\n 'the tarball to the admin API.',\n );\n process.exit(1);\n default:\n console.error('Usage: rvnxx-nodes <manifest>');\n process.exit(1);\n }\n}\n\nmain().catch((err) => {\n console.error(err);\n process.exit(1);\n});\n"],"mappings":";;;;;;AAiBA,YAAY,QAAQ;AACpB,SAAS,eAAe;AACxB,SAAS,qBAAqB;AAI9B,IAAM,cAAc,QAAQ,IAAI;AAEhC,SAAS,KAAK,SAAwB;AACpC,UAAQ,MAAM,UAAU,OAAO,EAAE;AACjC,UAAQ,KAAK,CAAC;AAChB;AAIA,eAAe,cAA6B;AAC1C,QAAM,YAAY,QAAQ,aAAa,QAAQ,UAAU;AACzD,MAAI,CAAI,cAAW,SAAS,GAAG;AAC7B,SAAK,+EAA+E;AAAA,EACtF;AAEA,QAAM,MAAO,MAAM,OAAO,cAAc,SAAS,EAAE;AAKnD,MAAI,CAAC,MAAM,QAAQ,IAAI,KAAK,GAAG;AAC7B,SAAK,iGAAiG;AAAA,EACxG;AACA,MAAI,IAAI,gBAAgB,UAAa,CAAC,MAAM,QAAQ,IAAI,WAAW,GAAG;AACpE,SAAK,kGAAkG;AAAA,EACzG;AACA,MAAI,IAAI,cAAc,UAAa,CAAC,MAAM,QAAQ,IAAI,SAAS,GAAG;AAChE,SAAK,uGAAuG;AAAA,EAC9G;AAEA,QAAM,cAAe,IAAI,eAA6C,CAAC;AACvE,QAAM,YAAa,IAAI,aAAoD,CAAC;AAC5E,QAAM,WAAW,cAAc,IAAI,OAAkB,aAAa,SAAS;AAE3E,QAAM,SAAS,QAAQ,aAAa,MAAM;AAC1C,QAAM,UAAU,QAAQ,QAAQ,eAAe;AAC/C,EAAG,aAAU,QAAQ,EAAE,WAAW,KAAK,CAAC;AACxC,EAAG,iBAAc,SAAS,KAAK,UAAU,UAAU,MAAM,CAAC,GAAG,OAAO;AAEpE,QAAM,kBAAkB,SAAS,aAAa,UAAU;AACxD,QAAM,gBAAgB,SAAS,WAAW,UAAU;AACpD,UAAQ;AAAA,IACN,oCAA0B,SAAS,MAAM,MAAM,aAAa,eAAe,mBAAmB,aAAa;AAAA,EAC7G;AACA,aAAW,KAAK,SAAS,OAAO;AAC9B,YAAQ,IAAI,gBAAgB,EAAE,IAAI,IAAI,EAAE,OAAO,EAAE;AAAA,EACnD;AACA,aAAW,KAAK,SAAS,eAAe,CAAC,GAAG;AAC1C,YAAQ,IAAI,gBAAgB,EAAE,IAAI,IAAI,EAAE,OAAO,KAAK,EAAE,QAAQ,GAAG;AAAA,EACnE;AACA,aAAW,KAAK,SAAS,aAAa,CAAC,GAAG;AACxC,YAAQ,IAAI,gBAAgB,EAAE,IAAI,IAAI,EAAE,OAAO,KAAK,EAAE,KAAK,GAAG;AAAA,EAChE;AACF;AAIA,eAAe,OAAsB;AACnC,QAAM,UAAU,QAAQ,KAAK,CAAC;AAC9B,UAAQ,SAAS;AAAA,IACf,KAAK;AACH,YAAM,YAAY;AAClB;AAAA,IACF,KAAK;AACH,cAAQ;AAAA,QACN;AAAA,MAKF;AACA,cAAQ,KAAK,CAAC;AAAA,IAChB;AACE,cAAQ,MAAM,+BAA+B;AAC7C,cAAQ,KAAK,CAAC;AAAA,EAClB;AACF;AAEA,KAAK,EAAE,MAAM,CAAC,QAAQ;AACpB,UAAQ,MAAM,GAAG;AACjB,UAAQ,KAAK,CAAC;AAChB,CAAC;","names":[]}
|
package/dist/index.cjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/types.ts","../src/localized.ts","../src/credentialType.ts","../src/extract.ts","../src/manifest.ts","../src/credentials.ts","../src/errors.ts"],"sourcesContent":["export * from './types.js';\nexport * from './localized.js';\nexport * from './credentialType.js';\nexport * from './extract.js';\nexport * from './manifest.js';\nexport * from './credentials.js';\nexport * from './errors.js';\n","export type LocalizedString = string | Record<string, string>;\n\nexport type DataType = 'any' | 'object' | 'array' | 'string' | 'number' | 'boolean';\nexport type OutputKind = 'default' | 'branch' | 'error';\nexport type NodeCategory = 'trigger' | 'action' | 'transform' | 'control' | 'io';\nexport type ConfigType =\n | 'string'\n | 'number'\n | 'boolean'\n | 'select'\n | 'multiselect'\n | 'object'\n | 'array'\n | 'expression'\n | 'secret-ref'\n | 'credentials-ref';\n\nexport interface IInputPort {\n dataType: DataType;\n required?: boolean;\n description?: LocalizedString;\n}\n\nexport interface IOutputField {\n dataType: DataType;\n description?: LocalizedString;\n}\n\nexport interface IOutputPort {\n kind: OutputKind;\n dataType: DataType;\n name?: string;\n label?: LocalizedString;\n description?: LocalizedString;\n fields?: Record<string, IOutputField>;\n sourceFromConfig?: string;\n fallback?: {\n name: string;\n label?: LocalizedString;\n description?: LocalizedString;\n };\n}\n\nexport interface IConfigOption {\n value: string | number | boolean;\n label: LocalizedString;\n}\n\nexport interface IConfigValidation {\n pattern?: string;\n min?: number;\n max?: number;\n minLength?: number;\n maxLength?: number;\n}\n\nexport interface IConfigFieldBase {\n key: string;\n label: LocalizedString;\n type: ConfigType;\n description?: LocalizedString;\n required?: boolean;\n default?: unknown;\n placeholder?: LocalizedString;\n expressionAllowed?: boolean;\n multiline?: boolean;\n validation?: IConfigValidation;\n options?: IConfigOption[];\n /**\n * Only meaningful when `type === 'credentials-ref'`: the namespaced slug(s) of\n * the credential type(s) this field accepts (e.g. `revenexx:smtp`). The editor\n * lists tenant credential instances of these type(s); the blob stores the\n * chosen instance UUID. Pass a single string when only one type is accepted,\n * or an array to let the field accept instances of any of several types (e.g.\n * a node that works with both an OAuth and an API-token credential).\n */\n credentialType?: string | string[];\n}\n\nexport interface IConfigField extends IConfigFieldBase {\n properties?: IConfigFieldBase[];\n items?: IConfigFieldBase;\n}\n\nexport interface INodeDescription {\n slug: string;\n version: string;\n category: NodeCategory;\n name: LocalizedString;\n description?: LocalizedString;\n icon?: string;\n inputs: Record<string, IInputPort>;\n outputs: IOutputPort[];\n config?: IConfigField[];\n}\n\nexport interface INodeContext {\n signal: AbortSignal;\n logger: {\n info(message: string, meta?: Record<string, unknown>): void;\n warn(message: string, meta?: Record<string, unknown>): void;\n error(message: string, meta?: Record<string, unknown>): void;\n };\n secrets: {\n get(key: string): Promise<string>;\n };\n /**\n * Resolve the access data of a credential instance the node references via a\n * `credentials-ref` config field. The runtime fulfils this from the\n * credentials broker at execution time (inside the Temporal activity), so the\n * returned value — e.g. a short-lived access token — is always current and\n * never persisted into workflow history.\n */\n credentials: {\n get(credentialsId: string): Promise<Record<string, unknown>>;\n };\n}\n\nexport interface INodeResult {\n outputs: Record<string, unknown>;\n branch?: string;\n}\n\nexport interface INode {\n description: INodeDescription;\n execute(ctx: INodeContext, inputs: Record<string, unknown>): Promise<INodeResult>;\n}\n\n/**\n * Optional capability interface for nodes that iterate over a collection.\n * Implement this alongside {@link INode} to signal to the worker that this\n * node drives iteration — the worker will call {@link extractItems} instead\n * of relying on slug-based detection.\n *\n * This interface is the designated dispatch point for future child-workflow\n * execution: once per-iteration isolation is needed, the worker can swap the\n * inline loop for `executeChild` calls without any changes to the node or SDK.\n */\nexport interface INodeWithIteration {\n /**\n * Extracts the array of items to iterate over from the resolved inputs and\n * config. Must be pure and synchronous — it runs inside a Temporal Activity.\n */\n extractItems(inputs: Record<string, unknown>, config: Record<string, unknown>): unknown[];\n}\n\n/**\n * Type guard that checks whether a node implements {@link INodeWithIteration}.\n */\nexport function isNodeWithIteration(node: INode): node is INode & INodeWithIteration {\n return typeof (node as INode & Partial<INodeWithIteration>).extractItems === 'function';\n}\n\n// ---------------------------------------------------------------- Credentials\n\n/**\n * The authentication strategy a credential type uses. Determines which SDK\n * base class a concrete credential extends and how the broker resolves it.\n */\nexport type CredentialAuthKind =\n | 'static'\n | 'api-key'\n | 'basic'\n | 'oauth2-client-credentials'\n | 'oauth2-authcode';\n\n/**\n * Field types for the connection parameters a credential type declares. A\n * superset-subset of {@link ConfigType}: scalars plus `secret` for values that\n * must be masked in the UI and never returned in plaintext by the public API.\n */\nexport type CredentialFieldType = 'string' | 'number' | 'boolean' | 'select' | 'secret';\n\nexport interface ICredentialField {\n key: string;\n label: LocalizedString;\n type: CredentialFieldType;\n description?: LocalizedString;\n required?: boolean;\n default?: unknown;\n placeholder?: LocalizedString;\n validation?: IConfigValidation;\n options?: IConfigOption[];\n}\n\n/**\n * The published contract of a credential type: how the editor renders its\n * config form and which auth strategy the broker applies. The imperative\n * `test`/`resolve` logic lives in the bundled {@link ICredential} code, not in\n * this description.\n */\nexport interface ICredentialDescription {\n /** Stable namespaced identifier `<namespace>:<slug>` (e.g. `revenexx:smtp`). */\n slug: string;\n version: string;\n name: LocalizedString;\n description?: LocalizedString;\n icon?: string;\n authKind: CredentialAuthKind;\n fields: ICredentialField[];\n}\n\n/**\n * Runtime context handed to a credential's `test`/`resolve`. Runs in the\n * credentials broker (a Node side-container), never in workflow code.\n */\nexport interface ICredentialContext {\n signal: AbortSignal;\n logger: {\n info(message: string, meta?: Record<string, unknown>): void;\n warn(message: string, meta?: Record<string, unknown>): void;\n error(message: string, meta?: Record<string, unknown>): void;\n };\n /**\n * Persist updated durable credentials (e.g. a rotated `refresh_token`) back\n * to storage. The broker wires this to `PATCH /v1/internal/credentials/{id}/durable-creds`.\n * Absent during pre-save tests where no instance exists yet.\n */\n persistDurableCreds?(durableCreds: Record<string, unknown>): Promise<void>;\n}\n\nexport interface ICredentialTestResult {\n ok: boolean;\n message?: string;\n}\n\nexport interface ICredentialResolveResult {\n /** The access data handed to the node (e.g. `{ host, port, user, password }` or `{ accessToken }`). */\n credentials: Record<string, unknown>;\n /** ISO-8601 expiry of the resolved access data; absent for non-expiring (static) credentials. */\n expiresAt?: string;\n}\n\n/**\n * A credential type implementation. `config` is the validated, user-entered\n * field map; `durableCreds` holds system-managed long-lived secrets (e.g. a\n * `refresh_token` obtained via 3-legged OAuth) and is `null` until they exist.\n */\nexport interface ICredential {\n description: ICredentialDescription;\n test(ctx: ICredentialContext, config: Record<string, unknown>): Promise<ICredentialTestResult>;\n resolve(\n ctx: ICredentialContext,\n config: Record<string, unknown>,\n durableCreds: Record<string, unknown> | null,\n ): Promise<ICredentialResolveResult>;\n}\n\n/**\n * Optional capability for `oauth2-authcode` credentials that need the\n * interactive (3-legged) consent dance. The broker exposes these via\n * `/oauth/authorize-url` and `/oauth/exchange-code`; Laravel proxies them.\n */\nexport interface ICredentialOAuthAuthorize {\n buildAuthorizeUrl(\n ctx: ICredentialContext,\n config: Record<string, unknown>,\n params: { redirectUri: string; state: string },\n ): Promise<{ authorizeUrl: string; codeVerifier?: string }>;\n exchangeCode(\n ctx: ICredentialContext,\n config: Record<string, unknown>,\n params: { code: string; redirectUri: string; codeVerifier?: string },\n ): Promise<{ durableCreds: Record<string, unknown> }>;\n}\n\n/**\n * Type guard for credentials that implement the 3-legged OAuth consent flow.\n */\nexport function isOAuthAuthorizeCredential(\n credential: ICredential,\n): credential is ICredential & ICredentialOAuthAuthorize {\n const candidate = credential as ICredential & Partial<ICredentialOAuthAuthorize>;\n return (\n typeof candidate.buildAuthorizeUrl === 'function' &&\n typeof candidate.exchangeCode === 'function'\n );\n}\n\n// ------------------------------------------------------------------ Templates\n\n/** Difficulty level surfaced in the template gallery. */\nexport type TemplateLevel = 'beginner' | 'intermediate' | 'advanced';\n\n/** Trigger kind a template can ship; mirrors the server's `TriggerType`. */\nexport type TemplateTriggerType = 'manual' | 'schedule' | 'webhook' | 'event';\n\n/**\n * A trigger a template instantiates alongside its workflow. The `handle` is a\n * stable UUID the workflow blob's edges reference via `from.nodeId`. Per-type\n * `config` shapes: `schedule` → `{ cron, timezone? }`, `webhook` →\n * `{ method, secretRef?, public? }`, `event` → `{ subject }`, `manual` → none.\n * The integrations server deep-validates `config` per `type` on publish.\n */\nexport interface ITemplateTrigger {\n /** Stable UUID the blob's edges reference via `from.nodeId`. */\n handle: string;\n type: TemplateTriggerType;\n name?: string;\n config?: Record<string, unknown>;\n active?: boolean;\n}\n\n/**\n * A workflow template a node package publishes: a ready-made workflow blueprint\n * the editor offers as a starting point. Unlike {@link INode}/{@link ICredential}\n * a template carries no executable code — it is plain data, so a package exports\n * its `ITemplateDescription`s directly (no class wrapper).\n *\n * The `definition` is a workflow blob authored against the workflow-blob grammar\n * named by `blobVersion`; the integrations server validates it on publish.\n */\nexport interface ITemplateDescription {\n /** Stable namespaced identifier `<namespace>:<slug>` (e.g. `revenexx:slack-to-crm`). */\n slug: string;\n version: string;\n /** Free-form grouping label shown in the gallery (e.g. `sales`). */\n category: string;\n level: TemplateLevel;\n name: LocalizedString;\n /** One-line summary shown on the template card. */\n shortDescription?: LocalizedString;\n /** Long-form description in Markdown. */\n description?: LocalizedString;\n icon?: string;\n /** Free-form industry tags (e.g. `any`, `medical`). */\n industries?: string[];\n /** Free-form vendor tags (e.g. `pipedrive`, `slack`). */\n vendors?: string[];\n /** Workflow-blob schema version `definition` targets (e.g. `v0-draft`). */\n blobVersion: string;\n /** The workflow blob instantiated when a user picks this template. */\n definition: Record<string, unknown>;\n /**\n * Triggers instantiated alongside the workflow (their `handle`s are\n * referenced by `definition`'s edges). Omit for a manual-only template.\n */\n triggers?: ITemplateTrigger[];\n}\n","import type { LocalizedString } from './types.js';\n\n/**\n * Reduce a {@link LocalizedString} to a single plain string.\n *\n * Every consumer (worker, UI, the Laravel side) repeats the same \"is it a\n * string or a localized map?\" branch to render a name/label; this centralizes\n * it so the rules are defined once:\n *\n * - a plain string is trimmed and returned (or `undefined` if blank);\n * - a localized map prefers `fallbackLang` (default `en`), then the first\n * non-empty value;\n * - a missing, empty, or blank-only value yields `undefined`, so the caller\n * can apply its own fallback.\n */\nexport function normalizeLocalized(\n value: LocalizedString | undefined | null,\n fallbackLang = 'en',\n): string | undefined {\n if (typeof value === 'string') {\n const trimmed = value.trim();\n return trimmed === '' ? undefined : trimmed;\n }\n\n if (value && typeof value === 'object') {\n const preferred = value[fallbackLang];\n if (typeof preferred === 'string' && preferred.trim() !== '') {\n return preferred.trim();\n }\n\n for (const candidate of Object.values(value)) {\n if (typeof candidate === 'string' && candidate.trim() !== '') {\n return candidate.trim();\n }\n }\n }\n\n return undefined;\n}\n","/**\n * Reduce an `IConfigField.credentialType` (a single slug or an array of slugs)\n * to a normalized, deduplicated `string[]`.\n *\n * The union `string | string[]` exists so node authors can write the common\n * single-type case as a bare string, but every consumer (the editor that\n * lists tenant credential instances, the worker that resolves the chosen\n * instance, the Laravel side) needs a list to iterate. This centralizes the\n * \"is it a string or an array?\" branch — analogous to {@link normalizeLocalized}\n * for `LocalizedString` — so the rules are defined once:\n *\n * - a single non-empty slug becomes a one-element array;\n * - an array is trimmed of blank entries and deduplicated, preserving order;\n * - `undefined`, an empty string, or an empty/all-blank array yields `[]`, so\n * the caller can treat \"accepts nothing\" uniformly.\n */\nexport function normalizeCredentialType(value: string | string[] | undefined): string[] {\n const raw = value === undefined ? [] : Array.isArray(value) ? value : [value];\n const seen = new Set<string>();\n for (const entry of raw) {\n const trimmed = typeof entry === 'string' ? entry.trim() : '';\n if (trimmed !== '') {\n seen.add(trimmed);\n }\n }\n return [...seen];\n}\n","import type { ICredential, ICredentialDescription, INode, INodeDescription } from './types.js';\n\nexport function extractManifest(node: INode): INodeDescription {\n return node.description;\n}\n\nexport function extractManifests(nodes: INode[]): INodeDescription[] {\n return nodes.map(extractManifest);\n}\n\nexport function extractCredentialManifest(credential: ICredential): ICredentialDescription {\n return credential.description;\n}\n\nexport function extractCredentialManifests(credentials: ICredential[]): ICredentialDescription[] {\n return credentials.map(extractCredentialManifest);\n}\n","import { extractCredentialManifests, extractManifests } from './extract.js';\nimport type {\n ICredential,\n ICredentialDescription,\n INode,\n INodeDescription,\n ITemplateDescription,\n} from './types.js';\n\n/**\n * Envelope version expected by the integrations server-side\n * `TarballInspector`. Earlier (pre-registry) builds emitted a bare array\n * without any `manifestVersion` field; `v0-draft` is currently the only\n * accepted value (see `SchemaServiceProvider::DOMAIN_NODE` in the\n * integrations service). Keeping it versioned lets the registry evolve the\n * shape without breaking older publishers.\n */\nexport const MANIFEST_VERSION = 'v0-draft';\n\nexport interface NodeManifest {\n manifestVersion: typeof MANIFEST_VERSION;\n nodes: INodeDescription[];\n /**\n * Credential types this package publishes. Optional and additive: packages\n * that ship no credentials omit it (older publishers never set it).\n */\n credentials?: ICredentialDescription[];\n /**\n * Workflow templates this package publishes. Optional and additive: packages\n * that ship no templates omit it. Templates are plain data (no executable\n * code), so they are carried verbatim from the package's `TEMPLATES` export.\n */\n templates?: ITemplateDescription[];\n}\n\n/**\n * Builds the manifest envelope from a package's `NODES` (and optional\n * `CREDENTIALS` / `TEMPLATES`) exports. The result is what gets written to\n * `dist/manifest.json` and uploaded to the registry inside the tarball.\n */\nexport function buildManifest(\n nodes: INode[],\n credentials: ICredential[] = [],\n templates: ITemplateDescription[] = [],\n): NodeManifest {\n const manifest: NodeManifest = {\n manifestVersion: MANIFEST_VERSION,\n nodes: extractManifests(nodes),\n };\n if (credentials.length > 0) {\n manifest.credentials = extractCredentialManifests(credentials);\n }\n if (templates.length > 0) {\n manifest.templates = templates;\n }\n return manifest;\n}\n","import { createHash, randomBytes } from 'node:crypto';\nimport type {\n ICredential,\n ICredentialContext,\n ICredentialDescription,\n ICredentialOAuthAuthorize,\n ICredentialResolveResult,\n ICredentialTestResult,\n} from './types.js';\n\n/**\n * Reusable credential base classes. Concrete credential types (in node\n * packages) extend one of these and only supply field mappings / endpoints —\n * the auth strategy itself lives here so it is implemented once.\n *\n * All of this runs in the credentials broker (a Node side-container), never in\n * workflow code, so wall-clock time and network I/O are fine.\n */\n\ntype Config = Record<string, unknown>;\ntype DurableCreds = Record<string, unknown> | null;\n\nfunction readString(source: Record<string, unknown>, key: string): string | undefined {\n const value = source[key];\n return typeof value === 'string' ? value : undefined;\n}\n\nfunction requireString(source: Record<string, unknown>, key: string, what: string): string {\n const value = readString(source, key);\n if (value === undefined || value === '') {\n throw new Error(`${what}: missing required value \"${key}\"`);\n }\n return value;\n}\n\n/** Compute the ISO expiry from an OAuth `expires_in` (seconds), if present. */\nfunction expiryFromExpiresIn(expiresIn: unknown): string | undefined {\n const seconds = typeof expiresIn === 'number' ? expiresIn : Number(expiresIn);\n if (!Number.isFinite(seconds) || seconds <= 0) {\n return undefined;\n }\n return new Date(Date.now() + seconds * 1000).toISOString();\n}\n\nexport interface OAuthTokenResponse {\n access_token?: string;\n refresh_token?: string;\n expires_in?: number;\n token_type?: string;\n /**\n * Provider-specific base URL the access token must target (e.g. Pipedrive\n * returns the company-scoped `api_domain` on both the initial exchange and\n * every refresh). Absent for providers that use a single global API host.\n */\n api_domain?: string;\n [key: string]: unknown;\n}\n\n/**\n * The base for every credential type. Stores the published description and\n * provides a small HTTP helper. `resolve` is abstract; `test` defaults to a\n * presence check and should be overridden where a real connection test exists.\n */\nexport abstract class BaseCredential implements ICredential {\n abstract readonly description: ICredentialDescription;\n\n abstract resolve(\n ctx: ICredentialContext,\n config: Config,\n durableCreds: DurableCreds,\n ): Promise<ICredentialResolveResult>;\n\n async test(_ctx: ICredentialContext, config: Config): Promise<ICredentialTestResult> {\n for (const field of this.description.fields) {\n if (field.required && (config[field.key] === undefined || config[field.key] === '')) {\n return { ok: false, message: `Missing required field \"${field.key}\"` };\n }\n }\n return { ok: true };\n }\n\n /** POST `application/x-www-form-urlencoded` and parse a JSON token response. */\n protected async postForm(\n ctx: ICredentialContext,\n url: string,\n form: Record<string, string | undefined>,\n headers: Record<string, string> = {},\n ): Promise<OAuthTokenResponse> {\n const body = new URLSearchParams();\n for (const [key, value] of Object.entries(form)) {\n if (value !== undefined) {\n body.set(key, value);\n }\n }\n\n const res = await fetch(url, {\n method: 'POST',\n signal: ctx.signal,\n headers: { 'Content-Type': 'application/x-www-form-urlencoded', Accept: 'application/json', ...headers },\n body,\n });\n\n const text = await res.text();\n if (!res.ok) {\n // Surface only the standard OAuth error fields (RFC 6749 §5.2), never the\n // raw body — token endpoints can echo request params or diagnostics that\n // may contain secrets/PII, which would then leak via logs/error reporting.\n throw new Error(`token endpoint ${url} -> ${res.status}${formatOAuthError(text)}`);\n }\n try {\n return JSON.parse(text) as OAuthTokenResponse;\n } catch {\n throw new Error(`token endpoint ${url} returned non-JSON body`);\n }\n }\n}\n\n/**\n * Extract `{ error, error_description }` (RFC 6749 §5.2) from a token-endpoint\n * error body for a safe, useful message. Returns an empty string when the body\n * is not a recognisable OAuth error object — the raw body is never included.\n */\nfunction formatOAuthError(body: string): string {\n try {\n const parsed = JSON.parse(body) as { error?: unknown; error_description?: unknown };\n const error = typeof parsed.error === 'string' ? parsed.error : undefined;\n if (!error) {\n return '';\n }\n const description = typeof parsed.error_description === 'string' ? parsed.error_description : undefined;\n return `: ${error}${description ? ` (${description})` : ''}`;\n } catch {\n return '';\n }\n}\n\n/**\n * \"Simple value retrieval\": the validated config fields ARE the access data\n * handed to the node (e.g. SMTP host/port/user/password, SFTP host/key). No\n * token, no expiry. Override `test` to add a real connection check.\n */\nexport abstract class SimpleValueCredential extends BaseCredential {\n async resolve(\n _ctx: ICredentialContext,\n config: Config,\n _durableCreds: DurableCreds,\n ): Promise<ICredentialResolveResult> {\n return { credentials: { ...config } };\n }\n}\n\n/**\n * API-key credential. By default exposes the configured key under\n * `{ apiKey }`; override {@link apiKeyField}/{@link credentialShape} to map.\n */\nexport abstract class ApiKeyCredential extends BaseCredential {\n protected apiKeyField(): string {\n return 'apiKey';\n }\n\n protected credentialShape(apiKey: string): Record<string, unknown> {\n return { apiKey };\n }\n\n async resolve(\n _ctx: ICredentialContext,\n config: Config,\n _durableCreds: DurableCreds,\n ): Promise<ICredentialResolveResult> {\n const apiKey = requireString(config, this.apiKeyField(), this.description.slug);\n return { credentials: this.credentialShape(apiKey) };\n }\n}\n\n/**\n * HTTP Basic auth credential. Exposes `{ username, password }` by default.\n */\nexport abstract class BasicAuthCredential extends BaseCredential {\n protected usernameField(): string {\n return 'username';\n }\n\n protected passwordField(): string {\n return 'password';\n }\n\n async resolve(\n _ctx: ICredentialContext,\n config: Config,\n _durableCreds: DurableCreds,\n ): Promise<ICredentialResolveResult> {\n const username = requireString(config, this.usernameField(), this.description.slug);\n const password = requireString(config, this.passwordField(), this.description.slug);\n return { credentials: { username, password } };\n }\n}\n\n/**\n * OAuth2 client-credentials grant (service-to-service, no user, no\n * refresh_token). Concrete types supply the token endpoint + client creds via\n * the config field map. Robust to downtime: a fresh access token is minted on\n * every resolve when the broker cache misses.\n */\nexport abstract class OAuth2ClientCredentialsCredential extends BaseCredential {\n protected abstract tokenUrl(config: Config): string;\n protected abstract clientId(config: Config): string;\n protected abstract clientSecret(config: Config): string;\n\n protected scope(_config: Config): string | undefined {\n return undefined;\n }\n\n /** Map the raw token response to the node-facing credentials. */\n protected credentialShape(token: OAuthTokenResponse): Record<string, unknown> {\n return { accessToken: token.access_token, tokenType: token.token_type ?? 'Bearer' };\n }\n\n async resolve(\n ctx: ICredentialContext,\n config: Config,\n _durableCreds: DurableCreds,\n ): Promise<ICredentialResolveResult> {\n const token = await this.postForm(ctx, this.tokenUrl(config), {\n grant_type: 'client_credentials',\n client_id: this.clientId(config),\n client_secret: this.clientSecret(config),\n scope: this.scope(config),\n });\n if (!token.access_token) {\n throw new Error(`${this.description.slug}: token endpoint returned no access_token`);\n }\n return { credentials: this.credentialShape(token), expiresAt: expiryFromExpiresIn(token.expires_in) };\n }\n\n async test(ctx: ICredentialContext, config: Config): Promise<ICredentialTestResult> {\n try {\n await this.resolve(ctx, config, null);\n return { ok: true };\n } catch (err) {\n return { ok: false, message: err instanceof Error ? err.message : String(err) };\n }\n }\n}\n\n/**\n * OAuth2 authorization-code grant (3-legged, interactive consent). Implements\n * the consent dance (with PKCE by default), code exchange, and refresh-token\n * based resolution. The long-lived `refresh_token` is stored in durableCreds;\n * if the provider rotates it, the new one is persisted via the context.\n */\nexport abstract class OAuth2AuthCodeCredential\n extends BaseCredential\n implements ICredentialOAuthAuthorize\n{\n protected abstract authorizeUrl(config: Config): string;\n protected abstract tokenUrl(config: Config): string;\n protected abstract clientId(config: Config): string;\n protected abstract clientSecret(config: Config): string;\n\n protected scope(_config: Config): string | undefined {\n return undefined;\n }\n\n protected usePkce(): boolean {\n return true;\n }\n\n protected credentialShape(token: OAuthTokenResponse): Record<string, unknown> {\n return { accessToken: token.access_token, tokenType: token.token_type ?? 'Bearer' };\n }\n\n async buildAuthorizeUrl(\n _ctx: ICredentialContext,\n config: Config,\n params: { redirectUri: string; state: string },\n ): Promise<{ authorizeUrl: string; codeVerifier?: string }> {\n const url = new URL(this.authorizeUrl(config));\n url.searchParams.set('response_type', 'code');\n url.searchParams.set('client_id', this.clientId(config));\n url.searchParams.set('redirect_uri', params.redirectUri);\n url.searchParams.set('state', params.state);\n const scope = this.scope(config);\n if (scope) {\n url.searchParams.set('scope', scope);\n }\n\n let codeVerifier: string | undefined;\n if (this.usePkce()) {\n codeVerifier = base64Url(randomBytes(32));\n const challenge = base64Url(createHash('sha256').update(codeVerifier).digest());\n url.searchParams.set('code_challenge', challenge);\n url.searchParams.set('code_challenge_method', 'S256');\n }\n\n return { authorizeUrl: url.toString(), codeVerifier };\n }\n\n async exchangeCode(\n ctx: ICredentialContext,\n config: Config,\n params: { code: string; redirectUri: string; codeVerifier?: string },\n ): Promise<{ durableCreds: Record<string, unknown> }> {\n const token = await this.postForm(ctx, this.tokenUrl(config), {\n grant_type: 'authorization_code',\n code: params.code,\n redirect_uri: params.redirectUri,\n client_id: this.clientId(config),\n client_secret: this.clientSecret(config),\n code_verifier: params.codeVerifier,\n });\n if (!token.refresh_token) {\n throw new Error(`${this.description.slug}: authorization_code exchange returned no refresh_token`);\n }\n return { durableCreds: { refreshToken: token.refresh_token } };\n }\n\n async resolve(\n ctx: ICredentialContext,\n config: Config,\n durableCreds: DurableCreds,\n ): Promise<ICredentialResolveResult> {\n const refreshToken = durableCreds ? readString(durableCreds, 'refreshToken') : undefined;\n if (!refreshToken) {\n throw new Error(`${this.description.slug}: not authorized — no refresh_token (needs_reauth)`);\n }\n\n const token = await this.postForm(ctx, this.tokenUrl(config), {\n grant_type: 'refresh_token',\n refresh_token: refreshToken,\n client_id: this.clientId(config),\n client_secret: this.clientSecret(config),\n scope: this.scope(config),\n });\n if (!token.access_token) {\n throw new Error(`${this.description.slug}: refresh returned no access_token`);\n }\n\n // Persist a rotated refresh_token (single-writer responsibility lies with\n // the broker/Laravel; here we just hand the new value back).\n if (token.refresh_token && token.refresh_token !== refreshToken && ctx.persistDurableCreds) {\n await ctx.persistDurableCreds({ refreshToken: token.refresh_token });\n }\n\n return { credentials: this.credentialShape(token), expiresAt: expiryFromExpiresIn(token.expires_in) };\n }\n\n async test(_ctx: ICredentialContext, config: Config): Promise<ICredentialTestResult> {\n // Pre-consent there is no refresh_token to exercise, so validate that the\n // full set of values the code-exchange/refresh will need is present and\n // well-formed: both endpoints are valid URLs and the client id/secret\n // resolve. (Subclasses read these from config; a missing required field\n // throws via requireString, which we surface as ok:false.)\n try {\n new URL(this.authorizeUrl(config));\n new URL(this.tokenUrl(config));\n this.clientId(config);\n this.clientSecret(config);\n return { ok: true, message: 'Configuration valid — complete the OAuth consent to finish setup.' };\n } catch (err) {\n return { ok: false, message: err instanceof Error ? err.message : String(err) };\n }\n }\n}\n\nfunction base64Url(buf: Buffer): string {\n return buf.toString('base64').replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n}\n","export class NodeError extends Error {\n readonly code: string;\n readonly meta?: Record<string, unknown>;\n\n constructor(code: string, message: string, meta?: Record<string, unknown>) {\n super(message);\n this.name = 'NodeError';\n this.code = code;\n this.meta = meta;\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/types.ts","../src/localized.ts","../src/credentialType.ts","../src/credentials.ts","../src/errors.ts"],"sourcesContent":["export type LocalizedString = string | Record<string, string>;\n\nexport type DataType = 'any' | 'object' | 'array' | 'string' | 'number' | 'boolean';\nexport type OutputKind = 'default' | 'branch' | 'error';\nexport type NodeCategory = 'trigger' | 'action' | 'transform' | 'control' | 'io';\nexport type ConfigType =\n | 'string'\n | 'number'\n | 'boolean'\n | 'select'\n | 'multiselect'\n | 'object'\n | 'array'\n | 'expression'\n | 'secret-ref'\n | 'credentials-ref';\n\nexport interface IInputPort {\n dataType: DataType;\n required?: boolean;\n description?: LocalizedString;\n}\n\nexport interface IOutputField {\n dataType: DataType;\n description?: LocalizedString;\n}\n\nexport interface IOutputPort {\n kind: OutputKind;\n dataType: DataType;\n name?: string;\n label?: LocalizedString;\n description?: LocalizedString;\n fields?: Record<string, IOutputField>;\n sourceFromConfig?: string;\n fallback?: {\n name: string;\n label?: LocalizedString;\n description?: LocalizedString;\n };\n}\n\nexport interface IConfigOption {\n value: string | number | boolean;\n label: LocalizedString;\n}\n\nexport interface IConfigValidation {\n pattern?: string;\n min?: number;\n max?: number;\n minLength?: number;\n maxLength?: number;\n}\n\nexport interface IConfigFieldBase {\n key: string;\n label: LocalizedString;\n type: ConfigType;\n description?: LocalizedString;\n required?: boolean;\n default?: unknown;\n placeholder?: LocalizedString;\n expressionAllowed?: boolean;\n multiline?: boolean;\n validation?: IConfigValidation;\n options?: IConfigOption[];\n /**\n * Only meaningful when `type === 'credentials-ref'`: the namespaced slug(s) of\n * the credential type(s) this field accepts (e.g. `revenexx:smtp`). The editor\n * lists tenant credential instances of these type(s); the blob stores the\n * chosen instance UUID. Pass a single string when only one type is accepted,\n * or an array to let the field accept instances of any of several types (e.g.\n * a node that works with both an OAuth and an API-token credential).\n */\n credentialType?: string | string[];\n}\n\nexport interface IConfigField extends IConfigFieldBase {\n properties?: IConfigFieldBase[];\n items?: IConfigFieldBase;\n}\n\nexport interface INodeDescription {\n slug: string;\n version: string;\n category: NodeCategory;\n name: LocalizedString;\n description?: LocalizedString;\n icon?: string;\n inputs: Record<string, IInputPort>;\n outputs: IOutputPort[];\n config?: IConfigField[];\n}\n\nexport interface INodeContext {\n signal: AbortSignal;\n logger: {\n info(message: string, meta?: Record<string, unknown>): void;\n warn(message: string, meta?: Record<string, unknown>): void;\n error(message: string, meta?: Record<string, unknown>): void;\n };\n secrets: {\n get(key: string): Promise<string>;\n };\n /**\n * Resolve the access data of a credential instance the node references via a\n * `credentials-ref` config field. The runtime fulfils this from the\n * credentials broker at execution time (inside the Temporal activity), so the\n * returned value — e.g. a short-lived access token — is always current and\n * never persisted into workflow history.\n */\n credentials: {\n get(credentialsId: string): Promise<Record<string, unknown>>;\n };\n}\n\nexport interface INodeResult {\n outputs: Record<string, unknown>;\n branch?: string;\n}\n\nexport interface INode {\n description: INodeDescription;\n execute(ctx: INodeContext, inputs: Record<string, unknown>): Promise<INodeResult>;\n}\n\n/**\n * Optional capability interface for nodes that iterate over a collection.\n * Implement this alongside {@link INode} to signal to the worker that this\n * node drives iteration — the worker will call {@link extractItems} instead\n * of relying on slug-based detection.\n *\n * This interface is the designated dispatch point for future child-workflow\n * execution: once per-iteration isolation is needed, the worker can swap the\n * inline loop for `executeChild` calls without any changes to the node or SDK.\n */\nexport interface INodeWithIteration {\n /**\n * Extracts the array of items to iterate over from the resolved inputs and\n * config. Must be pure and synchronous — it runs inside a Temporal Activity.\n */\n extractItems(inputs: Record<string, unknown>, config: Record<string, unknown>): unknown[];\n}\n\n/**\n * Type guard that checks whether a node implements {@link INodeWithIteration}.\n */\nexport function isNodeWithIteration(node: INode): node is INode & INodeWithIteration {\n return typeof (node as INode & Partial<INodeWithIteration>).extractItems === 'function';\n}\n\n// ---------------------------------------------------------------- Credentials\n\n/**\n * The authentication strategy a credential type uses. Determines which SDK\n * base class a concrete credential extends and how the broker resolves it.\n */\nexport type CredentialAuthKind =\n | 'static'\n | 'api-key'\n | 'basic'\n | 'oauth2-client-credentials'\n | 'oauth2-authcode';\n\n/**\n * Field types for the connection parameters a credential type declares. A\n * superset-subset of {@link ConfigType}: scalars plus `secret` for values that\n * must be masked in the UI and never returned in plaintext by the public API.\n */\nexport type CredentialFieldType = 'string' | 'number' | 'boolean' | 'select' | 'secret';\n\nexport interface ICredentialField {\n key: string;\n label: LocalizedString;\n type: CredentialFieldType;\n description?: LocalizedString;\n required?: boolean;\n default?: unknown;\n placeholder?: LocalizedString;\n validation?: IConfigValidation;\n options?: IConfigOption[];\n}\n\n/**\n * The published contract of a credential type: how the editor renders its\n * config form and which auth strategy the broker applies. The imperative\n * `test`/`resolve` logic lives in the bundled {@link ICredential} code, not in\n * this description.\n */\nexport interface ICredentialDescription {\n /** Stable namespaced identifier `<namespace>:<slug>` (e.g. `revenexx:smtp`). */\n slug: string;\n version: string;\n name: LocalizedString;\n description?: LocalizedString;\n icon?: string;\n authKind: CredentialAuthKind;\n fields: ICredentialField[];\n}\n\n/**\n * Runtime context handed to a credential's `test`/`resolve`. Runs in the\n * credentials broker (a Node side-container), never in workflow code.\n */\nexport interface ICredentialContext {\n signal: AbortSignal;\n logger: {\n info(message: string, meta?: Record<string, unknown>): void;\n warn(message: string, meta?: Record<string, unknown>): void;\n error(message: string, meta?: Record<string, unknown>): void;\n };\n /**\n * Persist updated durable credentials (e.g. a rotated `refresh_token`) back\n * to storage. The broker wires this to `PATCH /v1/internal/credentials/{id}/durable-creds`.\n * Absent during pre-save tests where no instance exists yet.\n */\n persistDurableCreds?(durableCreds: Record<string, unknown>): Promise<void>;\n}\n\nexport interface ICredentialTestResult {\n ok: boolean;\n message?: string;\n}\n\nexport interface ICredentialResolveResult {\n /** The access data handed to the node (e.g. `{ host, port, user, password }` or `{ accessToken }`). */\n credentials: Record<string, unknown>;\n /** ISO-8601 expiry of the resolved access data; absent for non-expiring (static) credentials. */\n expiresAt?: string;\n}\n\n/**\n * A credential type implementation. `config` is the validated, user-entered\n * field map; `durableCreds` holds system-managed long-lived secrets (e.g. a\n * `refresh_token` obtained via 3-legged OAuth) and is `null` until they exist.\n */\nexport interface ICredential {\n description: ICredentialDescription;\n test(ctx: ICredentialContext, config: Record<string, unknown>): Promise<ICredentialTestResult>;\n resolve(\n ctx: ICredentialContext,\n config: Record<string, unknown>,\n durableCreds: Record<string, unknown> | null,\n ): Promise<ICredentialResolveResult>;\n}\n\n/**\n * Optional capability for `oauth2-authcode` credentials that need the\n * interactive (3-legged) consent dance. The broker exposes these via\n * `/oauth/authorize-url` and `/oauth/exchange-code`; Laravel proxies them.\n */\nexport interface ICredentialOAuthAuthorize {\n buildAuthorizeUrl(\n ctx: ICredentialContext,\n config: Record<string, unknown>,\n params: { redirectUri: string; state: string },\n ): Promise<{ authorizeUrl: string; codeVerifier?: string }>;\n exchangeCode(\n ctx: ICredentialContext,\n config: Record<string, unknown>,\n params: { code: string; redirectUri: string; codeVerifier?: string },\n ): Promise<{ durableCreds: Record<string, unknown> }>;\n}\n\n/**\n * Type guard for credentials that implement the 3-legged OAuth consent flow.\n */\nexport function isOAuthAuthorizeCredential(\n credential: ICredential,\n): credential is ICredential & ICredentialOAuthAuthorize {\n const candidate = credential as ICredential & Partial<ICredentialOAuthAuthorize>;\n return (\n typeof candidate.buildAuthorizeUrl === 'function' &&\n typeof candidate.exchangeCode === 'function'\n );\n}\n\n// ------------------------------------------------------------------ Templates\n\n/** Difficulty level surfaced in the template gallery. */\nexport type TemplateLevel = 'beginner' | 'intermediate' | 'advanced';\n\n/** Trigger kind a template can ship; mirrors the server's `TriggerType`. */\nexport type TemplateTriggerType = 'manual' | 'schedule' | 'webhook' | 'event';\n\n/**\n * A trigger a template instantiates alongside its workflow. The `handle` is a\n * stable UUID the workflow blob's edges reference via `from.nodeId`. Per-type\n * `config` shapes: `schedule` → `{ cron, timezone? }`, `webhook` →\n * `{ method, secretRef?, public? }`, `event` → `{ subject }`, `manual` → none.\n * The integrations server deep-validates `config` per `type` on publish.\n */\nexport interface ITemplateTrigger {\n /** Stable UUID the blob's edges reference via `from.nodeId`. */\n handle: string;\n type: TemplateTriggerType;\n name?: string;\n config?: Record<string, unknown>;\n active?: boolean;\n}\n\n/**\n * A workflow template a node package publishes: a ready-made workflow blueprint\n * the editor offers as a starting point. Unlike {@link INode}/{@link ICredential}\n * a template carries no executable code — it is plain data, so a package exports\n * its `ITemplateDescription`s directly (no class wrapper).\n *\n * The `definition` is a workflow blob authored against the workflow-blob grammar\n * named by `blobVersion`; the integrations server validates it on publish.\n */\nexport interface ITemplateDescription {\n /** Stable namespaced identifier `<namespace>:<slug>` (e.g. `revenexx:slack-to-crm`). */\n slug: string;\n version: string;\n /** Free-form grouping label shown in the gallery (e.g. `sales`). */\n category: string;\n level: TemplateLevel;\n name: LocalizedString;\n /** One-line summary shown on the template card. */\n shortDescription?: LocalizedString;\n /** Long-form description in Markdown. */\n description?: LocalizedString;\n icon?: string;\n /** Free-form industry tags (e.g. `any`, `medical`). */\n industries?: string[];\n /** Free-form vendor tags (e.g. `pipedrive`, `slack`). */\n vendors?: string[];\n /** Workflow-blob schema version `definition` targets (e.g. `v0-draft`). */\n blobVersion: string;\n /** The workflow blob instantiated when a user picks this template. */\n definition: Record<string, unknown>;\n /**\n * Triggers instantiated alongside the workflow (their `handle`s are\n * referenced by `definition`'s edges). Omit for a manual-only template.\n */\n triggers?: ITemplateTrigger[];\n}\n","import type { LocalizedString } from './types.js';\n\n/**\n * Reduce a {@link LocalizedString} to a single plain string.\n *\n * Every consumer (worker, UI, the Laravel side) repeats the same \"is it a\n * string or a localized map?\" branch to render a name/label; this centralizes\n * it so the rules are defined once:\n *\n * - a plain string is trimmed and returned (or `undefined` if blank);\n * - a localized map prefers `fallbackLang` (default `en`), then the first\n * non-empty value;\n * - a missing, empty, or blank-only value yields `undefined`, so the caller\n * can apply its own fallback.\n */\nexport function normalizeLocalized(\n value: LocalizedString | undefined | null,\n fallbackLang = 'en',\n): string | undefined {\n if (typeof value === 'string') {\n const trimmed = value.trim();\n return trimmed === '' ? undefined : trimmed;\n }\n\n if (value && typeof value === 'object') {\n const preferred = value[fallbackLang];\n if (typeof preferred === 'string' && preferred.trim() !== '') {\n return preferred.trim();\n }\n\n for (const candidate of Object.values(value)) {\n if (typeof candidate === 'string' && candidate.trim() !== '') {\n return candidate.trim();\n }\n }\n }\n\n return undefined;\n}\n","/**\n * Reduce an `IConfigField.credentialType` (a single slug or an array of slugs)\n * to a normalized, deduplicated `string[]`.\n *\n * The union `string | string[]` exists so node authors can write the common\n * single-type case as a bare string, but every consumer (the editor that\n * lists tenant credential instances, the worker that resolves the chosen\n * instance, the Laravel side) needs a list to iterate. This centralizes the\n * \"is it a string or an array?\" branch — analogous to {@link normalizeLocalized}\n * for `LocalizedString` — so the rules are defined once:\n *\n * - a single non-empty slug becomes a one-element array;\n * - an array is trimmed of blank entries and deduplicated, preserving order;\n * - `undefined`, an empty string, or an empty/all-blank array yields `[]`, so\n * the caller can treat \"accepts nothing\" uniformly.\n */\nexport function normalizeCredentialType(value: string | string[] | undefined): string[] {\n const raw = value === undefined ? [] : Array.isArray(value) ? value : [value];\n const seen = new Set<string>();\n for (const entry of raw) {\n const trimmed = typeof entry === 'string' ? entry.trim() : '';\n if (trimmed !== '') {\n seen.add(trimmed);\n }\n }\n return [...seen];\n}\n","import { createHash, randomBytes } from 'node:crypto';\nimport type {\n ICredential,\n ICredentialContext,\n ICredentialDescription,\n ICredentialOAuthAuthorize,\n ICredentialResolveResult,\n ICredentialTestResult,\n} from './types.js';\n\n/**\n * Reusable credential base classes. Concrete credential types (in node\n * packages) extend one of these and only supply field mappings / endpoints —\n * the auth strategy itself lives here so it is implemented once.\n *\n * All of this runs in the credentials broker (a Node side-container), never in\n * workflow code, so wall-clock time and network I/O are fine.\n */\n\ntype Config = Record<string, unknown>;\ntype DurableCreds = Record<string, unknown> | null;\n\nfunction readString(source: Record<string, unknown>, key: string): string | undefined {\n const value = source[key];\n return typeof value === 'string' ? value : undefined;\n}\n\nfunction requireString(source: Record<string, unknown>, key: string, what: string): string {\n const value = readString(source, key);\n if (value === undefined || value === '') {\n throw new Error(`${what}: missing required value \"${key}\"`);\n }\n return value;\n}\n\n/** Compute the ISO expiry from an OAuth `expires_in` (seconds), if present. */\nfunction expiryFromExpiresIn(expiresIn: unknown): string | undefined {\n const seconds = typeof expiresIn === 'number' ? expiresIn : Number(expiresIn);\n if (!Number.isFinite(seconds) || seconds <= 0) {\n return undefined;\n }\n return new Date(Date.now() + seconds * 1000).toISOString();\n}\n\nexport interface OAuthTokenResponse {\n access_token?: string;\n refresh_token?: string;\n expires_in?: number;\n token_type?: string;\n /**\n * Provider-specific base URL the access token must target (e.g. Pipedrive\n * returns the company-scoped `api_domain` on both the initial exchange and\n * every refresh). Absent for providers that use a single global API host.\n */\n api_domain?: string;\n [key: string]: unknown;\n}\n\n/**\n * The base for every credential type. Stores the published description and\n * provides a small HTTP helper. `resolve` is abstract; `test` defaults to a\n * presence check and should be overridden where a real connection test exists.\n */\nexport abstract class BaseCredential implements ICredential {\n abstract readonly description: ICredentialDescription;\n\n abstract resolve(\n ctx: ICredentialContext,\n config: Config,\n durableCreds: DurableCreds,\n ): Promise<ICredentialResolveResult>;\n\n async test(_ctx: ICredentialContext, config: Config): Promise<ICredentialTestResult> {\n for (const field of this.description.fields) {\n if (field.required && (config[field.key] === undefined || config[field.key] === '')) {\n return { ok: false, message: `Missing required field \"${field.key}\"` };\n }\n }\n return { ok: true };\n }\n\n /** POST `application/x-www-form-urlencoded` and parse a JSON token response. */\n protected async postForm(\n ctx: ICredentialContext,\n url: string,\n form: Record<string, string | undefined>,\n headers: Record<string, string> = {},\n ): Promise<OAuthTokenResponse> {\n const body = new URLSearchParams();\n for (const [key, value] of Object.entries(form)) {\n if (value !== undefined) {\n body.set(key, value);\n }\n }\n\n const res = await fetch(url, {\n method: 'POST',\n signal: ctx.signal,\n headers: { 'Content-Type': 'application/x-www-form-urlencoded', Accept: 'application/json', ...headers },\n body,\n });\n\n const text = await res.text();\n if (!res.ok) {\n // Surface only the standard OAuth error fields (RFC 6749 §5.2), never the\n // raw body — token endpoints can echo request params or diagnostics that\n // may contain secrets/PII, which would then leak via logs/error reporting.\n throw new Error(`token endpoint ${url} -> ${res.status}${formatOAuthError(text)}`);\n }\n try {\n return JSON.parse(text) as OAuthTokenResponse;\n } catch {\n throw new Error(`token endpoint ${url} returned non-JSON body`);\n }\n }\n}\n\n/**\n * Extract `{ error, error_description }` (RFC 6749 §5.2) from a token-endpoint\n * error body for a safe, useful message. Returns an empty string when the body\n * is not a recognisable OAuth error object — the raw body is never included.\n */\nfunction formatOAuthError(body: string): string {\n try {\n const parsed = JSON.parse(body) as { error?: unknown; error_description?: unknown };\n const error = typeof parsed.error === 'string' ? parsed.error : undefined;\n if (!error) {\n return '';\n }\n const description = typeof parsed.error_description === 'string' ? parsed.error_description : undefined;\n return `: ${error}${description ? ` (${description})` : ''}`;\n } catch {\n return '';\n }\n}\n\n/**\n * \"Simple value retrieval\": the validated config fields ARE the access data\n * handed to the node (e.g. SMTP host/port/user/password, SFTP host/key). No\n * token, no expiry. Override `test` to add a real connection check.\n */\nexport abstract class SimpleValueCredential extends BaseCredential {\n async resolve(\n _ctx: ICredentialContext,\n config: Config,\n _durableCreds: DurableCreds,\n ): Promise<ICredentialResolveResult> {\n return { credentials: { ...config } };\n }\n}\n\n/**\n * API-key credential. By default exposes the configured key under\n * `{ apiKey }`; override {@link apiKeyField}/{@link credentialShape} to map.\n */\nexport abstract class ApiKeyCredential extends BaseCredential {\n protected apiKeyField(): string {\n return 'apiKey';\n }\n\n protected credentialShape(apiKey: string): Record<string, unknown> {\n return { apiKey };\n }\n\n async resolve(\n _ctx: ICredentialContext,\n config: Config,\n _durableCreds: DurableCreds,\n ): Promise<ICredentialResolveResult> {\n const apiKey = requireString(config, this.apiKeyField(), this.description.slug);\n return { credentials: this.credentialShape(apiKey) };\n }\n}\n\n/**\n * HTTP Basic auth credential. Exposes `{ username, password }` by default.\n */\nexport abstract class BasicAuthCredential extends BaseCredential {\n protected usernameField(): string {\n return 'username';\n }\n\n protected passwordField(): string {\n return 'password';\n }\n\n async resolve(\n _ctx: ICredentialContext,\n config: Config,\n _durableCreds: DurableCreds,\n ): Promise<ICredentialResolveResult> {\n const username = requireString(config, this.usernameField(), this.description.slug);\n const password = requireString(config, this.passwordField(), this.description.slug);\n return { credentials: { username, password } };\n }\n}\n\n/**\n * OAuth2 client-credentials grant (service-to-service, no user, no\n * refresh_token). Concrete types supply the token endpoint + client creds via\n * the config field map. Robust to downtime: a fresh access token is minted on\n * every resolve when the broker cache misses.\n */\nexport abstract class OAuth2ClientCredentialsCredential extends BaseCredential {\n protected abstract tokenUrl(config: Config): string;\n protected abstract clientId(config: Config): string;\n protected abstract clientSecret(config: Config): string;\n\n protected scope(_config: Config): string | undefined {\n return undefined;\n }\n\n /** Map the raw token response to the node-facing credentials. */\n protected credentialShape(token: OAuthTokenResponse): Record<string, unknown> {\n return { accessToken: token.access_token, tokenType: token.token_type ?? 'Bearer' };\n }\n\n async resolve(\n ctx: ICredentialContext,\n config: Config,\n _durableCreds: DurableCreds,\n ): Promise<ICredentialResolveResult> {\n const token = await this.postForm(ctx, this.tokenUrl(config), {\n grant_type: 'client_credentials',\n client_id: this.clientId(config),\n client_secret: this.clientSecret(config),\n scope: this.scope(config),\n });\n if (!token.access_token) {\n throw new Error(`${this.description.slug}: token endpoint returned no access_token`);\n }\n return { credentials: this.credentialShape(token), expiresAt: expiryFromExpiresIn(token.expires_in) };\n }\n\n async test(ctx: ICredentialContext, config: Config): Promise<ICredentialTestResult> {\n try {\n await this.resolve(ctx, config, null);\n return { ok: true };\n } catch (err) {\n return { ok: false, message: err instanceof Error ? err.message : String(err) };\n }\n }\n}\n\n/**\n * OAuth2 authorization-code grant (3-legged, interactive consent). Implements\n * the consent dance (with PKCE by default), code exchange, and refresh-token\n * based resolution. The long-lived `refresh_token` is stored in durableCreds;\n * if the provider rotates it, the new one is persisted via the context.\n */\nexport abstract class OAuth2AuthCodeCredential\n extends BaseCredential\n implements ICredentialOAuthAuthorize\n{\n protected abstract authorizeUrl(config: Config): string;\n protected abstract tokenUrl(config: Config): string;\n protected abstract clientId(config: Config): string;\n protected abstract clientSecret(config: Config): string;\n\n protected scope(_config: Config): string | undefined {\n return undefined;\n }\n\n protected usePkce(): boolean {\n return true;\n }\n\n protected credentialShape(token: OAuthTokenResponse): Record<string, unknown> {\n return { accessToken: token.access_token, tokenType: token.token_type ?? 'Bearer' };\n }\n\n async buildAuthorizeUrl(\n _ctx: ICredentialContext,\n config: Config,\n params: { redirectUri: string; state: string },\n ): Promise<{ authorizeUrl: string; codeVerifier?: string }> {\n const url = new URL(this.authorizeUrl(config));\n url.searchParams.set('response_type', 'code');\n url.searchParams.set('client_id', this.clientId(config));\n url.searchParams.set('redirect_uri', params.redirectUri);\n url.searchParams.set('state', params.state);\n const scope = this.scope(config);\n if (scope) {\n url.searchParams.set('scope', scope);\n }\n\n let codeVerifier: string | undefined;\n if (this.usePkce()) {\n codeVerifier = base64Url(randomBytes(32));\n const challenge = base64Url(createHash('sha256').update(codeVerifier).digest());\n url.searchParams.set('code_challenge', challenge);\n url.searchParams.set('code_challenge_method', 'S256');\n }\n\n return { authorizeUrl: url.toString(), codeVerifier };\n }\n\n async exchangeCode(\n ctx: ICredentialContext,\n config: Config,\n params: { code: string; redirectUri: string; codeVerifier?: string },\n ): Promise<{ durableCreds: Record<string, unknown> }> {\n const token = await this.postForm(ctx, this.tokenUrl(config), {\n grant_type: 'authorization_code',\n code: params.code,\n redirect_uri: params.redirectUri,\n client_id: this.clientId(config),\n client_secret: this.clientSecret(config),\n code_verifier: params.codeVerifier,\n });\n if (!token.refresh_token) {\n throw new Error(`${this.description.slug}: authorization_code exchange returned no refresh_token`);\n }\n return { durableCreds: { refreshToken: token.refresh_token } };\n }\n\n async resolve(\n ctx: ICredentialContext,\n config: Config,\n durableCreds: DurableCreds,\n ): Promise<ICredentialResolveResult> {\n const refreshToken = durableCreds ? readString(durableCreds, 'refreshToken') : undefined;\n if (!refreshToken) {\n throw new Error(`${this.description.slug}: not authorized — no refresh_token (needs_reauth)`);\n }\n\n const token = await this.postForm(ctx, this.tokenUrl(config), {\n grant_type: 'refresh_token',\n refresh_token: refreshToken,\n client_id: this.clientId(config),\n client_secret: this.clientSecret(config),\n scope: this.scope(config),\n });\n if (!token.access_token) {\n throw new Error(`${this.description.slug}: refresh returned no access_token`);\n }\n\n // Persist a rotated refresh_token (single-writer responsibility lies with\n // the broker/Laravel; here we just hand the new value back).\n if (token.refresh_token && token.refresh_token !== refreshToken && ctx.persistDurableCreds) {\n await ctx.persistDurableCreds({ refreshToken: token.refresh_token });\n }\n\n return { credentials: this.credentialShape(token), expiresAt: expiryFromExpiresIn(token.expires_in) };\n }\n\n async test(_ctx: ICredentialContext, config: Config): Promise<ICredentialTestResult> {\n // Pre-consent there is no refresh_token to exercise, so validate that the\n // full set of values the code-exchange/refresh will need is present and\n // well-formed: both endpoints are valid URLs and the client id/secret\n // resolve. (Subclasses read these from config; a missing required field\n // throws via requireString, which we surface as ok:false.)\n try {\n new URL(this.authorizeUrl(config));\n new URL(this.tokenUrl(config));\n this.clientId(config);\n this.clientSecret(config);\n return { ok: true, message: 'Configuration valid — complete the OAuth consent to finish setup.' };\n } catch (err) {\n return { ok: false, message: err instanceof Error ? err.message : String(err) };\n }\n }\n}\n\nfunction base64Url(buf: Buffer): string {\n return buf.toString('base64').replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n}\n","export class NodeError extends Error {\n readonly code: string;\n readonly meta?: Record<string, unknown>;\n\n constructor(code: string, message: string, meta?: Record<string, unknown>) {\n super(message);\n this.name = 'NodeError';\n this.code = code;\n this.meta = meta;\n }\n}\n"],"mappings":"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","names":[]}
|