@attocash/n8n-nodes-atto 0.3.2 → 0.4.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/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # @attocash/n8n-nodes-atto
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/@attocash/n8n-nodes-atto.svg)](https://www.npmjs.com/package/@attocash/n8n-nodes-atto)
4
- [![n8n Atto Node CI](https://github.com/attocash/integrations/actions/workflows/n8n-node-package.yml/badge.svg)](https://github.com/attocash/integrations/actions/workflows/n8n-node-package.yml)
5
- [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/attocash/integrations/blob/main/LICENSE)
4
+ [![n8n Atto Node CI](https://github.com/attocash/integrations-n8n/actions/workflows/n8n-node-package.yml/badge.svg)](https://github.com/attocash/integrations-n8n/actions/workflows/n8n-node-package.yml)
5
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/attocash/integrations-n8n/blob/main/LICENSE)
6
6
 
7
7
  Use Atto from n8n workflows.
8
8
 
@@ -83,6 +83,8 @@ Trigger events:
83
83
  - **Transaction**: watches by hash, address stream, or all supported transactions.
84
84
  - **Account Entry**: watches by hash, address stream, or all supported account entries.
85
85
 
86
+ Triggers stay connected to the selected Atto NDJSON endpoint. If a stream closes, the node reconnects with exponential backoff from 1 to 30 seconds; a received event resets the delay.
87
+
86
88
  ## Example Workflows
87
89
 
88
90
  Importable examples live in [`examples`](./examples):
@@ -99,7 +101,7 @@ After importing an example, attach your **Atto API** credential and replace any
99
101
  Install dependencies and run the checks:
100
102
 
101
103
  ```bash
102
- npm install
104
+ npm install --ignore-scripts
103
105
  npm run build
104
106
  npm test
105
107
  npm run lint
@@ -107,6 +109,8 @@ npm run lint
107
109
 
108
110
  `npm test` builds the package and runs unit, smoke, and integration tests. The integration test uses `AttoNodeMockAsyncBuilder` and `AttoWorkerMockAsyncBuilder` from `@attocash/commons-test`. It uses Docker when available and falls back to a local Podman socket.
109
111
 
112
+ Dependencies ship prebuilt, so lifecycle scripts are disabled to avoid transitive package-manager guard scripts.
113
+
110
114
  To require the mock-container integration path:
111
115
 
112
116
  ```bash
@@ -139,8 +143,8 @@ If you have shell access inside the n8n container:
139
143
 
140
144
  ```bash
141
145
  cd /tmp
142
- git clone https://github.com/attocash/integrations.git
143
- cd integrations/n8n-node
146
+ git clone https://github.com/attocash/integrations-n8n.git
147
+ cd integrations-n8n
144
148
  npm run install:n8n
145
149
  ```
146
150
 
@@ -157,17 +161,17 @@ Restart n8n after the script finishes.
157
161
 
158
162
  ## Maintainers
159
163
 
160
- This package is versioned independently from other integrations in this repository. Use normal semver in `n8n-node/package.json`; release tags use the package-specific format `n8n-node-vX.Y.Z`.
164
+ Use conventional commits to select the next release version. Release-producing changes use the `n8n-node` scope:
161
165
 
162
166
  ```bash
163
- cd n8n-node
164
- npm version patch --no-git-tag-version
165
- git add package.json package-lock.json
166
- git commit -m "Release n8n Atto node vX.Y.Z"
167
- git push origin main
167
+ git commit -m "fix(n8n-node): describe the fix"
168
168
  ```
169
169
 
170
- On pushes to `main`, GitHub Actions tests and packs the attempted version, uploads the `.tgz` artifact, then waits for approval in the `release` environment. After approval, it creates the tag, publishes `@attocash/n8n-nodes-atto` to npm, and creates the GitHub release.
170
+ On pushes to `main`, GitHub Actions calculates the next version from the latest `n8n-node-vX.Y.Z` tag and tests the attempted package. When the manifests need updating, CI commits both files on `release/n8n-node-vX.Y.Z` and opens a release PR. After that protected PR is reviewed and merged, the next `main` run verifies the committed version, waits for approval in the `release` environment, creates the matching tag and GitHub release, and publishes the tested `.tgz` to npm.
171
+
172
+ Do not bump `package.json` manually. Snapshot versions exist only in their workflow runners; successful releases commit the version through the protected release PR so the repository and npm stay synchronized.
173
+
174
+ Allow GitHub Actions to create pull requests under **Settings → Actions → General → Workflow permissions**. A version PR created with `GITHUB_TOKEN` may require a maintainer to approve its workflow run before the required `test / test` check starts.
171
175
 
172
176
  Configure npm Trusted Publishing for `.github/workflows/n8n-node-package.yml`.
173
177
 
@@ -31,6 +31,38 @@ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
31
31
  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
32
32
  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33
33
 
34
+ ## js-joda
35
+
36
+ `@js-joda/core` is licensed under the BSD 3-Clause License.
37
+
38
+ Copyright (c) 2016, Philipp Thürwächter & Pattrick Hüper
39
+
40
+ All rights reserved.
41
+
42
+ Redistribution and use in source and binary forms, with or without
43
+ modification, are permitted provided that the following conditions are met:
44
+
45
+ 1. Redistributions of source code must retain the above copyright notice,
46
+ this list of conditions and the following disclaimer.
47
+ 2. Redistributions in binary form must reproduce the above copyright notice,
48
+ this list of conditions and the following disclaimer in the documentation
49
+ and/or other materials provided with the distribution.
50
+ 3. Neither the name of js-joda nor the names of its contributors may be used
51
+ to endorse or promote products derived from this software without specific
52
+ prior written permission.
53
+
54
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
55
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
56
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
57
+ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
58
+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
59
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
60
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
61
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
62
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
63
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
64
+ POSSIBILITY OF SUCH DAMAGE.
65
+
34
66
  ## Noble Hashes
35
67
 
36
68
  `@noble/hashes` is licensed under the MIT License.
@@ -8,7 +8,7 @@ class AttoApi {
8
8
  light: 'file:../nodes/Atto/atto.svg',
9
9
  dark: 'file:../nodes/Atto/atto.dark.svg',
10
10
  };
11
- documentationUrl = 'https://github.com/attocash/integrations/blob/main/n8n-node/USAGE.md';
11
+ documentationUrl = 'https://github.com/attocash/integrations-n8n/blob/main/USAGE.md';
12
12
  properties = [
13
13
  {
14
14
  displayName: 'Node Base URL',
@@ -7,12 +7,12 @@ const SECRET_PARAMETER_NAMES = ['secretSource', 'walletSecretType', 'walletSecre
7
7
  const OPERATION_PARAMETER_NAMES = {
8
8
  deriveAddress: SECRET_PARAMETER_NAMES,
9
9
  deriveAccount: SECRET_PARAMETER_NAMES,
10
- getAccount: ['address', 'simplify'],
11
- getReceivables: ['addressSource', 'addresses', 'minAmount', 'minAmountUnit', 'maxItems', 'timeoutMs', 'simplify'],
12
- getTransactions: ['queryMode', 'addresses', 'hash', 'fromHeight', 'toHeight', 'maxItems', 'timeoutMs', 'simplify'],
10
+ getAccount: ['address'],
11
+ getReceivables: ['addressSource', 'addresses', 'minAmount', 'minAmountUnit', 'maxItems', 'timeoutMs'],
12
+ getTransactions: ['queryMode', 'addresses', 'hash', 'fromHeight', 'toHeight', 'maxItems', 'timeoutMs'],
13
13
  sendTransaction: [...SECRET_PARAMETER_NAMES, 'destinationAddress', 'amount', 'amountUnit', 'timeoutMs'],
14
14
  receivePending: [...SECRET_PARAMETER_NAMES, 'representativeAddress', 'timeoutMs'],
15
- getAccountEntries: ['queryMode', 'addresses', 'hash', 'fromHeight', 'toHeight', 'maxItems', 'timeoutMs', 'simplify'],
15
+ getAccountEntries: ['queryMode', 'addresses', 'hash', 'fromHeight', 'toHeight', 'maxItems', 'timeoutMs'],
16
16
  changeRepresentative: [...SECRET_PARAMETER_NAMES, 'representativeAddress'],
17
17
  };
18
18
  const DEFAULT_OPERATION_BY_RESOURCE = {
@@ -51,7 +51,6 @@ const DEFAULT_PARAMETER_VALUES = {
51
51
  representativeAddress: '',
52
52
  timeoutMs: 5000,
53
53
  maxItems: 25,
54
- simplify: true,
55
54
  };
56
55
  const AMOUNT_UNITS = [
57
56
  {
@@ -553,18 +552,6 @@ class Atto {
553
552
  },
554
553
  description: 'New representative address',
555
554
  },
556
- {
557
- displayName: 'Simplify',
558
- name: 'simplify',
559
- type: 'boolean',
560
- default: true,
561
- displayOptions: {
562
- show: {
563
- operation: ['getAccount', 'getReceivables', 'getTransactions', 'getAccountEntries'],
564
- },
565
- },
566
- description: 'Whether to return a compact set of commonly used fields instead of the complete Atto API response',
567
- },
568
555
  {
569
556
  displayName: 'Max Items',
570
557
  name: 'maxItems',
@@ -6,12 +6,12 @@
6
6
  "resources": {
7
7
  "credentialDocumentation": [
8
8
  {
9
- "url": "https://github.com/attocash/integrations/tree/main/n8n-node#credentials"
9
+ "url": "https://github.com/attocash/integrations-n8n#credentials"
10
10
  }
11
11
  ],
12
12
  "primaryDocumentation": [
13
13
  {
14
- "url": "https://github.com/attocash/integrations/blob/main/n8n-node/USAGE.md"
14
+ "url": "https://github.com/attocash/integrations-n8n/blob/main/USAGE.md"
15
15
  }
16
16
  ]
17
17
  }
@@ -1,14 +1,31 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.executeAttoOperation = executeAttoOperation;
4
- exports.pollAttoEvent = pollAttoEvent;
4
+ exports.attoStreamRequest = attoStreamRequest;
5
+ exports.attoHttpStreamRequest = attoHttpStreamRequest;
6
+ exports.startAttoEventStream = startAttoEventStream;
5
7
  const n8n_workflow_1 = require("n8n-workflow");
6
8
  const protocol_1 = require("./protocol");
7
9
  const DEFAULT_STREAM_TIMEOUT_MS = 5000;
8
10
  const DEFAULT_PUBLISH_TIMEOUT_MS = 60000;
9
- const POLL_MAX_ITEMS = 100;
10
- const POLL_TIMEOUT_MS = 2000;
11
- const MAX_SEEN_POLL_ITEMS = 500;
11
+ const INITIAL_RECONNECT_DELAY_MS = 1000;
12
+ const MAX_RECONNECT_DELAY_MS = 30000;
13
+ function scheduleTimeout(callback, delayMs) {
14
+ const controller = new AbortController();
15
+ const timeout = AbortSignal.timeout(delayMs);
16
+ const onTimeout = () => {
17
+ if (!controller.signal.aborted)
18
+ callback();
19
+ };
20
+ timeout.addEventListener('abort', onTimeout, { once: true });
21
+ controller.signal.addEventListener('abort', () => timeout.removeEventListener('abort', onTimeout), { once: true });
22
+ return controller;
23
+ }
24
+ const DEFAULT_STREAM_RUNTIME = {
25
+ open: openAttoEventStream,
26
+ setTimeout: scheduleTimeout,
27
+ clearTimeout: (timer) => timer.abort(),
28
+ };
12
29
  function credentials(value) {
13
30
  return (value ?? {});
14
31
  }
@@ -39,13 +56,14 @@ function positiveHeight(value, fieldName) {
39
56
  throw new Error(`${fieldName} must be a positive integer`);
40
57
  return valueText;
41
58
  }
42
- function simplifyOutput(parameters) {
43
- return parameters.simplify !== false;
44
- }
45
59
  function requireNodeUrl(value) {
46
60
  const url = text(value.nodeUrl);
47
61
  if (!url)
48
62
  throw new Error('Atto credentials must include a Node Base URL');
63
+ const parsed = URL.canParse(url) ? new URL(url) : undefined;
64
+ if (!parsed || (parsed.protocol !== 'http:' && parsed.protocol !== 'https:')) {
65
+ throw new Error('Atto credentials must include a valid HTTP Node Base URL');
66
+ }
49
67
  return url.replace(/\/+$/, '');
50
68
  }
51
69
  function requireWorkerUrl(value) {
@@ -189,6 +207,24 @@ async function requestStream(context, value, request) {
189
207
  throw apiError(context, error, `Atto API stream failed: ${request.method} /${request.path}`);
190
208
  }
191
209
  }
210
+ async function openAttoEventStream(context, value, request, signal) {
211
+ const response = await context.helpers.httpRequest({
212
+ url: `${requireNodeUrl(value)}/${request.path}`,
213
+ method: request.method,
214
+ headers: {
215
+ ...requestHeaders(value, 'application/x-ndjson'),
216
+ ...(request.body ? { 'Content-Type': 'application/json' } : {}),
217
+ },
218
+ encoding: 'stream',
219
+ json: false,
220
+ abortSignal: signal,
221
+ ...(request.body ? { body: (0, protocol_1.stringifyAttoJson)(request.body) } : {}),
222
+ });
223
+ if (!response || typeof response !== 'object' || !(Symbol.asyncIterator in response)) {
224
+ throw new Error('Atto stream response is not readable');
225
+ }
226
+ return response;
227
+ }
192
228
  function contextRequired(context) {
193
229
  if (!context)
194
230
  throw new Error('This Atto operation requires an n8n execution context');
@@ -410,7 +446,6 @@ async function receivableFromInput(context, value, parameters) {
410
446
  }
411
447
  async function executeAttoOperation(context, operation, parameters, credentialData) {
412
448
  const value = credentials(credentialData);
413
- const simplify = simplifyOutput(parameters);
414
449
  if (operation === 'deriveAddress' || operation === 'deriveAccount') {
415
450
  const derived = await (0, protocol_1.deriveAddressFromSecret)(parameters, value);
416
451
  return { address: derived.value, publicKey: derived.publicKey, keyIndex: derived.keyIndex, secretType: derived.secretType };
@@ -419,21 +454,21 @@ async function executeAttoOperation(context, operation, parameters, credentialDa
419
454
  if (operation === 'getAccount') {
420
455
  const address = (0, protocol_1.parseAddress)(parameters.address ?? parameters.lookupAddress);
421
456
  const account = await accountByAddress(execution, value, address);
422
- return account ? (0, protocol_1.accountOutput)(account, simplify) : { found: false, address: address.value };
457
+ return account ? (0, protocol_1.accountOutput)(account) : { found: false, address: address.value };
423
458
  }
424
459
  if (operation === 'getReceivables') {
425
- return (await requestStream(execution, value, await receivableStreamRequest(parameters, value))).map((item) => (0, protocol_1.receivableOutput)(item, simplify));
460
+ return (await requestStream(execution, value, await receivableStreamRequest(parameters, value))).map((item) => (0, protocol_1.receivableOutput)(item));
426
461
  }
427
462
  if (operation === 'getTransactions') {
428
463
  if (queryMode(parameters.queryMode) === 'hash') {
429
464
  const hash = (0, protocol_1.parseHash)(parameters.hash);
430
465
  const transaction = (await requestJson(execution, value, requireNodeUrl(value), 'GET', `transactions/${hash}`));
431
- return [(0, protocol_1.transactionOutput)(transaction, simplify)];
466
+ return [(0, protocol_1.transactionOutput)(transaction)];
432
467
  }
433
- return (await requestStream(execution, value, await transactionStreamRequest(parameters, value))).map((item) => (0, protocol_1.transactionOutput)(item, simplify));
468
+ return (await requestStream(execution, value, await transactionStreamRequest(parameters, value))).map((item) => (0, protocol_1.transactionOutput)(item));
434
469
  }
435
470
  if (operation === 'getAccountEntries') {
436
- return (await requestStream(execution, value, await accountEntryStreamRequest(parameters, value))).map((item) => (0, protocol_1.accountEntryOutput)(item, simplify));
471
+ return (await requestStream(execution, value, await accountEntryStreamRequest(parameters, value))).map((item) => (0, protocol_1.accountEntryOutput)(item));
437
472
  }
438
473
  const derived = await (0, protocol_1.deriveAddressFromSecret)(parameters, value);
439
474
  const account = await accountByAddress(execution, value, derived);
@@ -447,7 +482,7 @@ async function executeAttoOperation(context, operation, parameters, credentialDa
447
482
  const block = (0, protocol_1.createSendBlock)(account, destination, amount, timestamp);
448
483
  const transaction = await publishBlock(execution, value, block, derived.signer, timeoutMs);
449
484
  return {
450
- ...(0, protocol_1.transactionOutput)(transaction, true, 'published'),
485
+ ...(0, protocol_1.transactionOutput)(transaction, 'published'),
451
486
  fromAddress: derived.value,
452
487
  destinationAddress: destination.value,
453
488
  amount: (0, protocol_1.amountOutput)(amount),
@@ -464,7 +499,7 @@ async function executeAttoOperation(context, operation, parameters, credentialDa
464
499
  const block = (0, protocol_1.createReceiveBlock)(account, receivable, representative, timestamp);
465
500
  const transaction = await publishBlock(execution, value, block, derived.signer, timeoutMs);
466
501
  return {
467
- ...(0, protocol_1.transactionOutput)(transaction, true, 'received'),
502
+ ...(0, protocol_1.transactionOutput)(transaction, 'received'),
468
503
  address: derived.value,
469
504
  representativeAddress: representative.value,
470
505
  amount: (0, protocol_1.amountOutput)(receivable.amount),
@@ -477,66 +512,234 @@ async function executeAttoOperation(context, operation, parameters, credentialDa
477
512
  const block = (0, protocol_1.createChangeBlock)(account, representative, timestamp);
478
513
  const transaction = await publishBlock(execution, value, block, derived.signer, timeoutMs);
479
514
  return {
480
- ...(0, protocol_1.transactionOutput)(transaction, true, 'representative_changed'),
515
+ ...(0, protocol_1.transactionOutput)(transaction, 'representative_changed'),
481
516
  address: derived.value,
482
517
  representativeAddress: representative.value,
483
518
  };
484
519
  }
485
520
  throw new Error(`Unsupported Atto operation: ${operation}`);
486
521
  }
487
- async function pollAccounts(context, parameters, value) {
488
- const addresses = await addressesForSource(parameters, value, true);
489
- if (addresses?.length === 1) {
490
- const account = await accountByAddress(context, value, addresses[0]);
491
- return account ? [(0, protocol_1.accountOutput)(account, true)] : [];
492
- }
493
- return (await requestStream(context, value, {
494
- method: addresses ? 'POST' : 'GET',
495
- path: 'accounts/stream',
496
- ...(addresses ? { body: { addresses: addresses.map((address) => address.value) } } : {}),
497
- maxItems: POLL_MAX_ITEMS,
498
- timeoutMs: POLL_TIMEOUT_MS,
499
- })).map((item) => (0, protocol_1.accountOutput)(item, true));
500
- }
501
- function pollItemKey(event, item) {
502
- if (event === 'account')
503
- return `${String(item.address)}:${String(item.frontier)}:${String(item.balance?.raw ?? '')}`;
504
- return String(item.hash ?? JSON.stringify(item));
505
- }
506
- function newPollItems(context, event, items) {
507
- const staticData = context.getWorkflowStaticData('node');
508
- const stateKey = `seen_${event}`;
509
- const existing = Array.isArray(staticData[stateKey]) ? staticData[stateKey].map(String) : [];
510
- const seen = new Set(existing);
511
- const fresh = items.filter((item) => !seen.has(pollItemKey(event, item)));
512
- const updated = [...existing, ...items.map((item) => pollItemKey(event, item))].slice(-MAX_SEEN_POLL_ITEMS);
513
- staticData[stateKey] = updated;
514
- if (existing.length === 0 && context.getMode() !== 'manual')
515
- return [];
516
- return fresh;
517
- }
518
- async function pollAttoEvent(context, event, parameters, credentialData) {
522
+ function addressStreamRoute(event, addresses) {
523
+ if (!addresses)
524
+ return { event, route: 'all' };
525
+ return {
526
+ event,
527
+ route: addresses.length === 1 ? 'publicKey' : 'addresses',
528
+ addresses,
529
+ };
530
+ }
531
+ async function attoStreamRequest(event, parameters, credentialData) {
519
532
  const value = credentials(credentialData);
520
- let items;
521
533
  if (event === 'receivable') {
522
- items = (await requestStream(context, value, await receivableStreamRequest(parameters, value, POLL_MAX_ITEMS, POLL_TIMEOUT_MS))).map((item) => (0, protocol_1.receivableOutput)(item, true));
534
+ const request = addressStreamRoute(event, await addressesForSource(parameters, value));
535
+ return {
536
+ ...request,
537
+ minAmount: optionalText(parameters.minAmount)
538
+ ? (0, protocol_1.parseAmount)(parameters.minAmount, parameters.minAmountUnit ?? 'RAW', 'Minimum Amount').toString()
539
+ : '0',
540
+ };
523
541
  }
524
- else if (event === 'account') {
525
- items = await pollAccounts(context, parameters, value);
542
+ if (event === 'account') {
543
+ return addressStreamRoute(event, await addressesForSource(parameters, value, true));
526
544
  }
527
- else if (event === 'transaction') {
528
- const mode = queryMode(parameters.queryMode);
529
- if (mode === 'hash') {
530
- const transaction = (await requestJson(context, value, requireNodeUrl(value), 'GET', `transactions/${(0, protocol_1.parseHash)(parameters.hash)}`));
531
- items = [(0, protocol_1.transactionOutput)(transaction, true)];
545
+ const mode = queryMode(parameters.queryMode);
546
+ if (mode === 'hash') {
547
+ return {
548
+ event,
549
+ route: 'hash',
550
+ hash: (0, protocol_1.parseHash)(parameters.hash),
551
+ };
552
+ }
553
+ const request = addressStreamRoute(event, await addressesForQuery(parameters, value));
554
+ if (request.route === 'all')
555
+ return request;
556
+ return {
557
+ ...request,
558
+ fromHeight: positiveHeight(parameters.fromHeight, 'From Height') ?? '1',
559
+ toHeight: positiveHeight(parameters.toHeight, 'To Height'),
560
+ };
561
+ }
562
+ function requiredStreamAddresses(request) {
563
+ const addresses = request.addresses ?? [];
564
+ if (addresses.length === 0)
565
+ throw new Error('Atto stream route requires at least one address');
566
+ return addresses;
567
+ }
568
+ function streamHeightQuery(request) {
569
+ const fromHeight = request.fromHeight ?? '1';
570
+ return `fromHeight=${encodeURIComponent(fromHeight)}${request.toHeight ? `&toHeight=${encodeURIComponent(request.toHeight)}` : ''}`;
571
+ }
572
+ function streamHeightSearch(request) {
573
+ return {
574
+ search: requiredStreamAddresses(request).map((address) => ({
575
+ address: address.value,
576
+ fromHeight: request.fromHeight ?? '1',
577
+ ...(request.toHeight ? { toHeight: request.toHeight } : {}),
578
+ })),
579
+ };
580
+ }
581
+ function attoHttpStreamRequest(request) {
582
+ if (request.event === 'account') {
583
+ if (request.route === 'all')
584
+ return { method: 'GET', path: 'accounts/stream' };
585
+ const addresses = requiredStreamAddresses(request);
586
+ if (request.route === 'publicKey') {
587
+ return { method: 'GET', path: `accounts/${addresses[0].publicKey}/stream` };
532
588
  }
533
- else {
534
- items = (await requestStream(context, value, await transactionStreamRequest(parameters, value, POLL_MAX_ITEMS, POLL_TIMEOUT_MS))).map((item) => (0, protocol_1.transactionOutput)(item, true));
589
+ if (request.route === 'addresses') {
590
+ return {
591
+ method: 'POST',
592
+ path: 'accounts/stream',
593
+ body: { addresses: addresses.map((address) => address.value) },
594
+ };
535
595
  }
536
596
  }
537
- else {
538
- items = (await requestStream(context, value, await accountEntryStreamRequest(parameters, value, POLL_MAX_ITEMS, POLL_TIMEOUT_MS))).map((item) => (0, protocol_1.accountEntryOutput)(item, true));
597
+ if (request.event === 'receivable') {
598
+ const addresses = requiredStreamAddresses(request);
599
+ const path = `accounts/receivables/stream?minAmount=${encodeURIComponent(request.minAmount ?? '0')}`;
600
+ if (request.route === 'publicKey') {
601
+ return {
602
+ method: 'GET',
603
+ path: `accounts/${addresses[0].publicKey}/receivables/stream?minAmount=${encodeURIComponent(request.minAmount ?? '0')}`,
604
+ };
605
+ }
606
+ if (request.route === 'addresses') {
607
+ return {
608
+ method: 'POST',
609
+ path,
610
+ body: { addresses: addresses.map((address) => address.value) },
611
+ };
612
+ }
613
+ }
614
+ if (request.event === 'transaction') {
615
+ if (request.route === 'all')
616
+ return { method: 'GET', path: 'transactions/stream' };
617
+ if (request.route === 'hash' && request.hash) {
618
+ return { method: 'GET', path: `transactions/${request.hash}/stream` };
619
+ }
620
+ const addresses = requiredStreamAddresses(request);
621
+ if (request.route === 'publicKey') {
622
+ return {
623
+ method: 'GET',
624
+ path: `accounts/${addresses[0].publicKey}/transactions/stream?${streamHeightQuery(request)}`,
625
+ };
626
+ }
627
+ if (request.route === 'addresses') {
628
+ return {
629
+ method: 'POST',
630
+ path: 'accounts/transactions/stream',
631
+ body: streamHeightSearch(request),
632
+ };
633
+ }
634
+ }
635
+ if (request.event === 'accountEntry') {
636
+ if (request.route === 'all')
637
+ return { method: 'GET', path: 'accounts/entries/stream' };
638
+ if (request.route === 'hash' && request.hash) {
639
+ return { method: 'GET', path: `accounts/entries/${request.hash}/stream` };
640
+ }
641
+ const addresses = requiredStreamAddresses(request);
642
+ if (request.route === 'publicKey') {
643
+ return {
644
+ method: 'GET',
645
+ path: `accounts/${addresses[0].publicKey}/entries/stream?${streamHeightQuery(request)}`,
646
+ };
647
+ }
648
+ if (request.route === 'addresses') {
649
+ return {
650
+ method: 'POST',
651
+ path: 'accounts/entries/stream',
652
+ body: streamHeightSearch(request),
653
+ };
654
+ }
539
655
  }
540
- return newPollItems(context, event, items);
656
+ throw new Error(`Unsupported Atto ${request.event} stream route: ${request.route}`);
657
+ }
658
+ async function startAttoEventStream(context, event, parameters, credentialData, onEvent, runtime = DEFAULT_STREAM_RUNTIME) {
659
+ const value = credentials(credentialData);
660
+ requireNodeUrl(value);
661
+ requestHeaders(value, 'application/x-ndjson');
662
+ const request = attoHttpStreamRequest(await attoStreamRequest(event, parameters, credentialData));
663
+ let activeController;
664
+ let activeConnection;
665
+ let reconnectTimer;
666
+ let reconnectDelayMs = INITIAL_RECONNECT_DELAY_MS;
667
+ let generation = 0;
668
+ let closed = false;
669
+ const scheduleReconnect = () => {
670
+ if (closed || activeController || reconnectTimer !== undefined)
671
+ return;
672
+ const delayMs = reconnectDelayMs;
673
+ reconnectDelayMs = Math.min(reconnectDelayMs * 2, MAX_RECONNECT_DELAY_MS);
674
+ reconnectTimer = runtime.setTimeout(() => {
675
+ reconnectTimer = undefined;
676
+ connect();
677
+ }, delayMs);
678
+ };
679
+ const connect = () => {
680
+ if (closed || activeController)
681
+ return;
682
+ const streamGeneration = ++generation;
683
+ const controller = new AbortController();
684
+ activeController = controller;
685
+ activeConnection = (async () => {
686
+ try {
687
+ const response = await runtime.open(context, value, request, controller.signal);
688
+ let pending = '';
689
+ for await (const chunk of response) {
690
+ if (closed || streamGeneration !== generation)
691
+ return;
692
+ pending += streamChunk(chunk);
693
+ const lines = pending.split('\n');
694
+ pending = lines.pop() ?? '';
695
+ for (const line of lines) {
696
+ if (!line.trim())
697
+ continue;
698
+ const parsed = (0, protocol_1.parseAttoJson)(line);
699
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
700
+ throw new Error(`Atto ${event} stream item must contain a JSON object`);
701
+ }
702
+ onEvent((0, protocol_1.streamEventOutput)(event, parsed));
703
+ reconnectDelayMs = INITIAL_RECONNECT_DELAY_MS;
704
+ }
705
+ }
706
+ if (pending.trim() && !closed && streamGeneration === generation) {
707
+ const parsed = (0, protocol_1.parseAttoJson)(pending);
708
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
709
+ throw new Error(`Atto ${event} stream item must contain a JSON object`);
710
+ }
711
+ onEvent((0, protocol_1.streamEventOutput)(event, parsed));
712
+ reconnectDelayMs = INITIAL_RECONNECT_DELAY_MS;
713
+ }
714
+ }
715
+ catch {
716
+ }
717
+ finally {
718
+ if (streamGeneration === generation) {
719
+ activeController = undefined;
720
+ activeConnection = undefined;
721
+ scheduleReconnect();
722
+ }
723
+ }
724
+ })();
725
+ };
726
+ connect();
727
+ return {
728
+ async close() {
729
+ if (closed)
730
+ return;
731
+ closed = true;
732
+ generation++;
733
+ if (reconnectTimer !== undefined) {
734
+ runtime.clearTimeout(reconnectTimer);
735
+ reconnectTimer = undefined;
736
+ }
737
+ const connection = activeConnection;
738
+ activeController?.abort();
739
+ activeController = undefined;
740
+ activeConnection = undefined;
741
+ await connection;
742
+ },
743
+ };
541
744
  }
542
745
  //# sourceMappingURL=operations.js.map