• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

nats-io / nats.js / 17831411252

18 Sep 2025 02:09PM UTC coverage: 83.819% (-1.0%) from 84.865%
17831411252

push

github

web-flow
feat(jsapi): add Raft group and traffic account metadata to cluster types (#319)

- Added additional fields to the ClusterInfo type.

Signed-off-by: Alberto Ricart <alberto@synadia.com>

2407 of 3260 branches covered (73.83%)

Branch coverage included in aggregate %.

10258 of 11850 relevant lines covered (86.57%)

754254.84 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

83.53
/jetstream/src/jsbaseclient_api.ts
1
/*
2
 * Copyright 2021-2023 The NATS Authors
3
 * Licensed under the Apache License, Version 2.0 (the "License");
4
 * you may not use this file except in compliance with the License.
5
 * You may obtain a copy of the License at
6
 *
7
 * http://www.apache.org/licenses/LICENSE-2.0
8
 *
9
 * Unless required by applicable law or agreed to in writing, software
10
 * distributed under the License is distributed on an "AS IS" BASIS,
11
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
 * See the License for the specific language governing permissions and
13
 * limitations under the License.
14
 */
15

16
import {
20✔
17
  backoff,
20✔
18
  delay,
20✔
19
  Empty,
20✔
20
  errors,
20✔
21
  extend,
20✔
22
  RequestError,
20✔
23
} from "@nats-io/nats-core/internal";
20✔
24
import type {
25
  Msg,
26
  NatsConnection,
27
  NatsConnectionImpl,
28
  RequestOptions,
29
} from "@nats-io/nats-core/internal";
30
import type { ApiResponse } from "./jsapi_types.ts";
31
import type { JetStreamOptions } from "./types.ts";
32
import {
20✔
33
  ConsumerNotFoundError,
20✔
34
  JetStreamApiCodes,
20✔
35
  JetStreamApiError,
20✔
36
  JetStreamNotEnabled,
20✔
37
  StreamNotFoundError,
20✔
38
} from "./jserrors.ts";
20✔
39

40
const defaultPrefix = "$JS.API";
20✔
41
const defaultTimeout = 5000;
20✔
42

43
export function defaultJsOptions(opts?: JetStreamOptions): JetStreamOptions {
20✔
44
  opts = opts || {} as JetStreamOptions;
2,221✔
45
  if (opts.domain) {
2,923✔
46
    opts.apiPrefix = `$JS.${opts.domain}.API`;
2,926✔
47
    delete opts.domain;
2,926✔
48
  }
2,926✔
49
  return extend({ apiPrefix: defaultPrefix, timeout: defaultTimeout }, opts);
12,904✔
50
}
3,226✔
51

52
export type StreamNames = {
53
  streams: string[];
54
};
55

56
export type StreamNameBySubject = {
57
  subject: string;
58
};
59

60
export class BaseApiClientImpl {
20✔
61
  nc: NatsConnectionImpl;
20✔
62
  opts: JetStreamOptions;
3,223✔
63
  prefix: string;
3,223✔
64
  timeout: number;
20✔
65

66
  constructor(nc: NatsConnection, opts?: JetStreamOptions) {
20✔
67
    this.nc = nc as NatsConnectionImpl;
3,223✔
68
    this.opts = defaultJsOptions(opts);
3,223✔
69
    this._parseOpts();
3,223✔
70
    this.prefix = this.opts.apiPrefix!;
3,223✔
71
    this.timeout = this.opts.timeout!;
3,223✔
72
  }
3,223✔
73

74
  getOptions(): JetStreamOptions {
20✔
75
    return Object.assign({}, this.opts);
116✔
76
  }
116✔
77

78
  _parseOpts() {
20✔
79
    let prefix = this.opts.apiPrefix;
3,223✔
80
    if (!prefix || prefix.length === 0) {
2,218✔
81
      throw errors.InvalidArgumentError.format("prefix", "cannot be empty");
2,219✔
82
    }
2,219✔
83
    const c = prefix[prefix.length - 1];
5,422✔
84
    if (c === ".") {
2,218!
85
      prefix = prefix.substr(0, prefix.length - 1);
2,221✔
86
    }
2,221✔
87
    this.opts.apiPrefix = prefix;
5,422✔
88
  }
3,223✔
89

90
  async _request(
20✔
91
    subj: string,
20✔
92
    data: unknown = null,
20✔
93
    opts?: Partial<RequestOptions> & { retries?: number },
20✔
94
  ): Promise<unknown> {
20✔
95
    opts = opts || {} as RequestOptions;
2,605✔
96
    opts.timeout = this.timeout;
2,605✔
97

98
    let a: Uint8Array = Empty;
2,605✔
99
    if (data) {
2,605✔
100
      a = new TextEncoder().encode(JSON.stringify(data));
4,111✔
101
    }
4,111✔
102

103
    let { retries } = opts as {
2,605✔
104
      retries: number;
105
    };
106

107
    retries = retries || 1;
2,605✔
108
    retries = retries === -1 ? Number.MAX_SAFE_INTEGER : retries;
×
109
    const bo = backoff();
2,605✔
110

111
    for (let i = 0; i < retries; i++) {
2,605✔
112
      try {
2,605✔
113
        const m = await this.nc.request(
2,605✔
114
          subj,
2,605✔
115
          a,
2,605✔
116
          opts as RequestOptions,
2,605✔
117
        );
118
        return this.parseJsResponse(m);
4,460✔
119
      } catch (err) {
2,605✔
120
        const re = err instanceof RequestError ? err as RequestError : null;
1,960!
121
        if (
×
122
          (err instanceof errors.TimeoutError || re?.isNoResponders()) &&
×
123
          i + 1 < retries
×
124
        ) {
×
125
          await delay(bo.backoff(i));
×
126
        } else {
×
127
          throw re?.isNoResponders()
1,960!
128
            ? new JetStreamNotEnabled("jetstream is not enabled", {
1,960✔
129
              cause: err,
1,961✔
130
            })
1,960✔
131
            : err;
1,960✔
132
        }
2,876✔
133
      }
2,876✔
134
    }
2,605!
135
  }
×
136

137
  async findStream(subject: string): Promise<string> {
18✔
138
    const q = { subject } as StreamNameBySubject;
63✔
139
    const r = await this._request(`${this.prefix}.STREAM.NAMES`, q);
21✔
140
    const names = r as StreamNames;
21✔
141
    if (!names.streams || names.streams.length !== 1) {
21✔
142
      throw StreamNotFoundError.fromMessage("no stream matches subject");
22✔
143
    }
22✔
144
    return names.streams[0];
23✔
145
  }
21✔
146

147
  getConnection(): NatsConnection {
×
148
    return this.nc;
×
149
  }
×
150

151
  parseJsResponse(m: Msg): unknown {
20✔
152
    const v = JSON.parse(new TextDecoder().decode(m.data));
123,010✔
153
    const r = v as ApiResponse;
123,010✔
154
    if (r.error) {
123,010✔
155
      switch (r.error.err_code) {
123,300✔
156
        case JetStreamApiCodes.ConsumerNotFound:
109,427✔
157
          throw new ConsumerNotFoundError(r.error);
109,448✔
158
        case JetStreamApiCodes.StreamNotFound:
123,300✔
159
          throw new StreamNotFoundError(r.error);
123,448✔
160
        case JetStreamApiCodes.JetStreamNotEnabledForAccount: {
218,855✔
161
          const jserr = new JetStreamApiError(r.error);
109,428✔
162
          throw new JetStreamNotEnabled(jserr.message, { cause: jserr });
328,284✔
163
        }
109,428✔
164
        default:
123,300✔
165
          throw new JetStreamApiError(r.error);
123,420✔
166
      }
123,300✔
167
    }
123,300✔
168
    return v;
245,710✔
169
  }
123,010✔
170
}
20✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc