@tchain/tcabci-read-js-client 2.7.12 → 2.8.0

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,65 +1,81 @@
1
- # TCABCI Read Node Javascript WebSocket Client
1
+ # TCABCI Read Node JavaScript Client
2
2
 
3
- TransferChain Fastest Read Network WebSocket Client
4
- Read Node Address: [https://read-node-01.transferchain.io](https://read-node-01.transferchain.io)
5
- Read Node WebSocket Address: [wss://read-node-01.transferchain.io/ws](wss://read-node-01.transferchain.io/ws)
3
+ HTTP queries, transaction broadcasts and WebSocket subscriptions for the
4
+ TransferChain read network. Uses ESM and supports Node.js 24+ or browsers with
5
+ Fetch, WebSocket, AbortController, TextEncoder and TextDecoder.
6
6
 
7
- ## Networks
8
-
9
- - medusa - v2
10
-
11
- ## Installation
12
-
13
- ```shell
14
- $ npm i @tchain/tcabci-read-js-client
7
+ ```sh
8
+ npm install @tchain/tcabci-read-js-client
15
9
  ```
16
10
 
17
- ## Example
18
-
19
- **Subscribe, Listen and Unsubscribe Example**
20
-
21
11
  ```js
22
12
  import TCaBCIClient from '@tchain/tcabci-read-js-client'
23
13
 
24
14
  const client = new TCaBCIClient()
25
- // OR
26
- const client = new TCaBCIClient([
27
- 'https://read-node-01.transferchain.io',
28
- 'wss://read-node-01.transferchain.io/ws'
29
- ])
30
- // OR
31
- // With addresses and WebSocket library and network name, version
32
- const client = new TCaBCIClient(
33
- [
34
- 'https://read-node-01.transferchain.io',
35
- 'wss://read-node-01.transferchain.io/ws'
36
- ],
37
- WebSocket,
38
- 'transferchain',
39
- 'v1'
40
- )
41
15
 
42
- client.Start()
16
+ client.SetSuccessCallback(() => {
17
+ // Obtain a valid address-to-signature map from your signing layer.
18
+ client.Subscribe(['<public-address>'], {
19
+ '<public-address>': '<signature>'
20
+ })
21
+ })
22
+ client.SetListenCallback((block, transaction, message) => {
23
+ // Handle the received immutable data snapshot.
24
+ })
25
+ client.SetErrorCallback(error => {
26
+ // Handle a sanitized transport/protocol error.
27
+ })
28
+
29
+ await client.Start() // Starts connecting; the success callback reports OPEN.
30
+ // When finished:
31
+ await client.Dispose() // Also clears application callbacks.
32
+ ```
43
33
 
44
- client.Subscribe(['<your-public-address-one>', '<your-public-address-two>'])
34
+ For an injected WebSocket implementation, pass its constructor as the second
35
+ argument. The first argument is an optional `[httpsURL, wssURL]` endpoint pair;
36
+ the third and fourth arguments select the chain. The fifth argument accepts
37
+ client limits and transport options.
45
38
 
46
- client.SetListenCallback(() => {
47
- // If a transaction has been sent to your addresses, the callback you set here will be called.
39
+ ```js
40
+ const client = new TCaBCIClient([], WebSocket, 'medusa', 'v2', {
41
+ timeoutMs: 30000,
42
+ maxRequestBytes: 16 * 1024 * 1024,
43
+ maxResponseBytes: 64 * 1024 * 1024,
44
+ maxConcurrentRequests: 32,
45
+ maxMessageBytes: 16 * 1024 * 1024,
46
+ maxEnqueuedMessages: 100,
47
+ maxPendingCallbacks: 1000
48
48
  })
49
49
 
50
- client.SetErrorCallback(() => {
51
- // If network errors will be called.
50
+ const controller = new AbortController()
51
+ const result = await client.Tx('<transaction-id>', '<signature>', {
52
+ signal: controller.signal,
53
+ timeoutMs: 10000
52
54
  })
55
+ ```
53
56
 
54
- client.SetCloseCallback(() => {
55
- // If server side close will be called.
56
- })
57
+ Each address list accepts 1–251 entries; each address accepts 1–2048 characters.
58
+ Active subscriptions are deduplicated and bounded to 251 addresses. Signed data
59
+ is an address-to-signature string map. Caller objects are neither frozen nor
60
+ overwritten.
57
61
 
58
- client.Unsubscribe()
59
- client.Stop()
60
- ```
62
+ HTTP redirects are rejected. Remote plaintext endpoints require explicit
63
+ `allowInsecure: true`; exact localhost/loopback endpoints support local
64
+ integration. Read-only operations may retry transient failures; broadcasts are
65
+ attempted once. The deadline includes retries and response consumption. HTTP
66
+ errors retain status, without response bodies or raw parser causes.
67
+
68
+ `Stop()` is idempotent, aborts HTTP work and releases subscription state. Await
69
+ pending HTTP promises to observe their cancellation and completed byte cleanup.
70
+ `Dispose()` additionally drops application callback references. Close codes use
71
+ browser-compatible values: 1000 or 3000–4999. Offline binary messages are
72
+ rejected; string queues have finite count and byte limits. Transport payload
73
+ logging is disabled, including when `SetDebug(true)` is called. Frozen snapshots
74
+ do not imply secure erasure of JavaScript strings.
75
+
76
+ TypeScript declarations accompany the ESM API. Tests are intentionally local
77
+ only under the ignored `test/` directory and are excluded from the package.
61
78
 
62
79
  ## License
63
80
 
64
- tcabci-read-js-client is licensed under the Apache License, Version 2.0. See [LICENSE](LICENSE) for the full license
65
- text.
81
+ Apache License, Version 2.0. See [LICENSE](LICENSE).
package/block.d.ts CHANGED
@@ -1,13 +1,22 @@
1
- import { Transaction } from './transaction'
1
+ import { Transaction } from './transaction.js'
2
+ import { ParseResult } from './types.js'
2
3
 
3
4
  export declare class Block {
4
- get Height(): number
5
- get TXS(): number
6
- get Hash(): string | null
7
- get Transactions(): Transaction[]
8
- get ChainName(): string
9
- get ChainVersion(): string
10
-
11
- static FromJSON(value: string): { block?: Block; error?: Error }
12
- static FromObject(obj: Record<string, any>): { block?: Block; error?: Error }
5
+ get Height(): number | undefined
6
+
7
+ get TXS(): number | undefined
8
+
9
+ get Hash(): string | undefined
10
+
11
+ get Transactions(): readonly Transaction[] | undefined
12
+
13
+ get ChainName(): string | undefined
14
+
15
+ get ChainVersion(): string | undefined
16
+
17
+ static FromJSON(value: string): ParseResult<'block', Readonly<Block>>
18
+
19
+ static FromObject(
20
+ obj: Record<string, unknown>
21
+ ): ParseResult<'block', Readonly<Block>>
13
22
  }
package/block.js CHANGED
@@ -1,5 +1,5 @@
1
+ import { SendThrow } from './util.js'
1
2
  import { Transaction } from './transaction.js'
2
- import { Debug } from '@streetbyters/js-debug'
3
3
 
4
4
  export class Block {
5
5
  _height
@@ -21,6 +21,7 @@ export class Block {
21
21
  return this._hash
22
22
  }
23
23
 
24
+ /** @returns {ReadonlyArray<Readonly<Transaction>>|undefined} Frozen parsed transactions. */
24
25
  get Transactions() {
25
26
  return this._transactions
26
27
  }
@@ -35,59 +36,54 @@ export class Block {
35
36
 
36
37
  /**
37
38
  * @param {string} value
38
- * @return {{block: ?Block, error: ?Error}}
39
+ * @return {import('./types.js').ParseResult<'block', Readonly<Block>>}
39
40
  * @constructor
40
41
  */
41
42
  static FromJSON(value) {
42
43
  try {
43
- const bl = new Block(),
44
- parsed = JSON.parse(value)
45
-
46
- bl._transactions = []
47
-
48
- if (typeof parsed.height !== 'undefined') bl._height = parsed.height
49
- if (typeof parsed.txs !== 'undefined') bl._txs = parsed.txs
50
- if (typeof parsed.hash !== 'undefined') bl._hash = parsed.hash
51
- if (typeof parsed.chain_name !== 'undefined')
52
- bl._chainName = parsed.chain_name
53
- if (typeof parsed.chain_version !== 'undefined')
54
- bl._chainVersion = parsed.chain_version
55
-
56
- if (typeof parsed.transactions !== 'undefined') {
57
- for (let i = 0; i < parsed.transactions.length; i++) {
58
- const { transaction, error } = Transaction.FromObject(
59
- parsed.transactions[i]
60
- )
61
- if (error) {
62
- return { block: null, error: error }
63
- }
64
-
65
- bl._transactions.push(transaction)
66
- }
67
- }
68
-
69
- return { block: bl, error: null }
70
- } catch (e) {
71
- Debug.error(e)
72
- return { block: null, error: e }
44
+ return Block.FromObject(JSON.parse(value))
45
+ } catch {
46
+ return { block: null, error: new Error('Invalid block JSON') }
73
47
  }
74
48
  }
75
49
 
76
- /**
77
- * @param {object} obj
78
- * @return {{block: ?Block, error: ?Error}}
79
- * @constructor
80
- */
50
+ /** @param {Record<string, unknown>} obj @returns {import('./types.js').ParseResult<'block', Readonly<Block>>} */
81
51
  static FromObject(obj) {
82
52
  try {
53
+ if (!obj || typeof obj !== 'object' || Array.isArray(obj))
54
+ SendThrow(new TypeError('Invalid block'))
55
+
56
+ for (const field of ['height', 'txs']) {
57
+ if (
58
+ typeof obj[field] !== 'undefined' &&
59
+ (!Number.isSafeInteger(obj[field]) || obj[field] < 0)
60
+ )
61
+ SendThrow(new TypeError('Invalid block counter'))
62
+ }
63
+
64
+ for (const field of ['hash', 'chain_name', 'chain_version']) {
65
+ if (typeof obj[field] !== 'undefined' && typeof obj[field] !== 'string')
66
+ SendThrow(new TypeError('Invalid block field'))
67
+ }
68
+
69
+ if (
70
+ typeof obj.transactions !== 'undefined' &&
71
+ (!Array.isArray(obj.transactions) || obj.transactions.length > 100000)
72
+ )
73
+ SendThrow(new TypeError('Invalid block transactions'))
74
+
83
75
  const bl = new Block()
84
76
 
85
77
  bl._transactions = []
86
78
 
87
79
  if (typeof obj.height !== 'undefined') bl._height = obj.height
80
+
88
81
  if (typeof obj.txs !== 'undefined') bl._txs = obj.txs
82
+
89
83
  if (typeof obj.hash !== 'undefined') bl._hash = obj.hash
84
+
90
85
  if (typeof obj.chain_name !== 'undefined') bl._chainName = obj.chain_name
86
+
91
87
  if (typeof obj.chain_version !== 'undefined')
92
88
  bl._chainVersion = obj.chain_version
93
89
 
@@ -96,6 +92,7 @@ export class Block {
96
92
  const { transaction, error } = Transaction.FromObject(
97
93
  obj.transactions[i]
98
94
  )
95
+
99
96
  if (error) {
100
97
  return { block: null, error: error }
101
98
  }
@@ -104,10 +101,11 @@ export class Block {
104
101
  }
105
102
  }
106
103
 
107
- return { block: bl, error: null }
108
- } catch (e) {
109
- Debug.error(e)
110
- return { block: null, error: e }
104
+ Object.freeze(bl._transactions)
105
+
106
+ return { block: Object.freeze(bl), error: null }
107
+ } catch {
108
+ return { block: null, error: new Error('Invalid block payload') }
111
109
  }
112
110
  }
113
111
  }
package/breaker.d.ts CHANGED
@@ -1,3 +1,7 @@
1
1
  export declare class Breaker {
2
- execute(fn: () => Promise<any>): Promise<any>
2
+ execute<T>(
3
+ fn: () => Promise<T>,
4
+ signal?: AbortSignal,
5
+ retry?: boolean
6
+ ): Promise<T>
3
7
  }
package/breaker.js CHANGED
@@ -1,52 +1,55 @@
1
- import {
2
- ConsecutiveBreaker,
3
- ExponentialBackoff,
4
- retry,
5
- handleType,
6
- circuitBreaker,
7
- wrap
8
- } from 'cockatiel'
9
- import { FetchError } from './errors.js'
1
+ import { ConsecutiveBreaker, handleType, circuitBreaker } from 'cockatiel'
2
+ import { FetchError, ERR_NETWORK } from './errors.js'
3
+
4
+ const retryable = (error) =>
5
+ error instanceof FetchError &&
6
+ ([502, 503, 504].includes(error.status) || error.message === ERR_NETWORK)
10
7
 
11
8
  export class Breaker {
12
- _options = {
13
- halfOpenAfter: 10 * 1000,
9
+ _circuitBreakerPolicy = circuitBreaker(handleType(FetchError, retryable), {
10
+ halfOpenAfter: 10000,
14
11
  breaker: new ConsecutiveBreaker(10)
15
- }
16
- _retryPolicy = retry(
17
- handleType(FetchError, err => [502, 503, 504].includes(err.status)).orType(
18
- Error,
19
- err => {
20
- return err.message.toLowerCase().search('failed to fetch') > -1
21
- }
22
- ),
23
- {
24
- maxAttempts: 10,
25
- backoff: new ExponentialBackoff({
26
- initialDelay: 500,
27
- maxDelay: 15000
28
- })
29
- }
30
- )
31
- _circuitBreakerPolicy = circuitBreaker(
32
- handleType(FetchError, err => [502, 503, 504].includes(err.status)).orType(
33
- Error,
34
- err => {
35
- return err.message.toLowerCase().search('failed to fetch') > -1
36
- }
37
- ),
38
- this._options
39
- )
40
- _retryWithBreaker = wrap(this._retryPolicy, this._circuitBreakerPolicy)
41
- constructor() {}
12
+ })
42
13
 
43
14
  /**
44
- * @param {function} fn
45
- * @return {Promise<*>}
15
+ * @template T
16
+ * @param {function(): Promise<T>} fn
17
+ * @param {AbortSignal} [signal] Cancels retry backoff immediately.
18
+ * @param {boolean} [retry=true] Disable for non-idempotent writes.
19
+ * @returns {Promise<T>}
46
20
  */
47
- execute(fn) {
48
- return this._retryWithBreaker.execute(fn).catch(err => {
49
- return Promise.reject(err)
50
- })
21
+ async execute(fn, signal, retry = true) {
22
+ for (let attempt = 0; ; attempt++) {
23
+ signal?.throwIfAborted()
24
+
25
+ try {
26
+ return await this._circuitBreakerPolicy.execute(fn, signal)
27
+ } catch (error) {
28
+ if (!retry || attempt >= 3 || signal?.aborted || !retryable(error))
29
+ throw error
30
+
31
+ await this._delay(
32
+ Math.min(500 * 2 ** attempt, 15000) * (0.5 + Math.random()),
33
+ signal
34
+ )
35
+ }
36
+ }
37
+ }
38
+
39
+ async _delay(ms, signal) {
40
+ let timer, abort
41
+
42
+ try {
43
+ await new Promise((resolve, reject) => {
44
+ abort = () => reject(new FetchError('Request aborted'))
45
+ timer = setTimeout(resolve, ms)
46
+
47
+ if (signal?.aborted) abort()
48
+ else signal?.addEventListener('abort', abort, { once: true })
49
+ })
50
+ } finally {
51
+ clearTimeout(timer)
52
+ signal?.removeEventListener('abort', abort)
53
+ }
51
54
  }
52
55
  }
package/bytea.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export declare class Bytea {
2
2
  constructor(bytes: Uint8Array, status: number)
3
+
3
4
  ToJSON(): string
4
5
  }
package/bytea.js ADDED
@@ -0,0 +1,16 @@
1
+ export class Bytea {
2
+ #bytes
3
+ #status
4
+
5
+ constructor(bytes, status) {
6
+ this.#bytes = bytes
7
+ this.#status = status
8
+ }
9
+
10
+ ToJSON() {
11
+ return JSON.stringify({
12
+ bytes: this.#bytes,
13
+ status: this.#status
14
+ })
15
+ }
16
+ }
package/callback.d.ts CHANGED
@@ -1,11 +1,16 @@
1
- import { Transaction } from './transaction'
2
- import { Block } from './block'
1
+ import { Transaction } from './transaction.js'
2
+ import { Block } from './block.js'
3
+ import Message from './message.js'
4
+ import { SocketCloseEvent } from './types.js'
3
5
 
4
- export declare type SuccessCallback = (event: Event) => void
5
- export declare type ErrorCallback = (event: Event) => void
6
- export declare type CloseCallback = (event: Event) => void
7
- export declare type ListenCallback = (
6
+ export type SuccessCallback = (event: Event) => void
7
+
8
+ export type ErrorCallback = (event: Error | Event) => void
9
+
10
+ export type CloseCallback = (event: SocketCloseEvent) => void
11
+
12
+ export type ListenCallback = (
8
13
  block: Block | null,
9
14
  tx: Transaction | null,
10
- msg: any | null
15
+ msg: Message | null
11
16
  ) => void