@nats-io/services 3.0.0-3 → 3.0.0-5

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/package.json CHANGED
@@ -1,9 +1,8 @@
1
1
  {
2
2
  "name": "@nats-io/services",
3
- "version": "3.0.0-3",
3
+ "version": "3.0.0-5",
4
4
  "files": [
5
5
  "lib/",
6
- "build/src/",
7
6
  "LICENSE",
8
7
  "README.md"
9
8
  ],
@@ -33,7 +32,7 @@
33
32
  },
34
33
  "description": "services library - this library implements all the base functionality for NATS services for javascript clients",
35
34
  "dependencies": {
36
- "@nats-io/nats-core": "~3.0.0-19"
35
+ "@nats-io/nats-core": "~3.0.0-24"
37
36
  },
38
37
  "devDependencies": {
39
38
  "@types/node": "^22.0.0",
@@ -1,52 +0,0 @@
1
- import type { NatsConnection, RequestManyOptions } from "@nats-io/nats-core";
2
- import { ServiceImpl } from "./service";
3
- import { ServiceClientImpl } from "./serviceclient";
4
- import type { Service, ServiceClient, ServiceConfig } from "./types";
5
-
6
- export type {
7
- Endpoint,
8
- EndpointInfo,
9
- EndpointOptions,
10
- EndpointStats,
11
- NamedEndpointStats,
12
- Service,
13
- ServiceClient,
14
- ServiceConfig,
15
- ServiceGroup,
16
- ServiceHandler,
17
- ServiceIdentity,
18
- ServiceInfo,
19
- ServiceMetadata,
20
- ServiceMsg,
21
- ServiceResponse,
22
- ServiceStats,
23
- } from "./types";
24
-
25
- export {
26
- ServiceError,
27
- ServiceErrorCodeHeader,
28
- ServiceErrorHeader,
29
- ServiceResponseType,
30
- ServiceVerb,
31
- } from "./types";
32
-
33
- export class Svc {
34
- nc: NatsConnection;
35
-
36
- constructor(nc: NatsConnection) {
37
- this.nc = nc;
38
- }
39
-
40
- add(config: ServiceConfig): Promise<Service> {
41
- try {
42
- const s = new ServiceImpl(this.nc, config);
43
- return s.start();
44
- } catch (err) {
45
- return Promise.reject(err);
46
- }
47
- }
48
-
49
- client(opts?: RequestManyOptions, prefix?: string): ServiceClient {
50
- return new ServiceClientImpl(this.nc, opts, prefix);
51
- }
52
- }
package/build/src/mod.ts DELETED
@@ -1,27 +0,0 @@
1
- export type {
2
- Endpoint,
3
- EndpointInfo,
4
- EndpointOptions,
5
- EndpointStats,
6
- NamedEndpointStats,
7
- Service,
8
- ServiceClient,
9
- ServiceConfig,
10
- ServiceGroup,
11
- ServiceHandler,
12
- ServiceIdentity,
13
- ServiceInfo,
14
- ServiceMetadata,
15
- ServiceMsg,
16
- ServiceResponse,
17
- ServiceStats,
18
- } from "./internal_mod";
19
-
20
- export {
21
- ServiceError,
22
- ServiceErrorCodeHeader,
23
- ServiceErrorHeader,
24
- ServiceResponseType,
25
- ServiceVerb,
26
- Svc,
27
- } from "./internal_mod";
@@ -1,712 +0,0 @@
1
- /*
2
- * Copyright 2022-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
- import {
16
- deferred,
17
- Empty,
18
- headers,
19
- JSONCodec,
20
- nanos,
21
- nuid,
22
- parseSemVer,
23
- QueuedIteratorImpl,
24
- } from "@nats-io/nats-core/internal";
25
- import type {
26
- Deferred,
27
- Msg,
28
- MsgHdrs,
29
- Nanos,
30
- NatsConnection,
31
- NatsError,
32
- Payload,
33
- PublishOptions,
34
- QueuedIterator,
35
- ReviverFn,
36
- Sub,
37
- } from "@nats-io/nats-core/internal";
38
-
39
- import {
40
- ServiceError,
41
- ServiceErrorCodeHeader,
42
- ServiceErrorHeader,
43
- ServiceResponseType,
44
- ServiceVerb,
45
- } from "./types";
46
-
47
- import type {
48
- Endpoint,
49
- EndpointInfo,
50
- EndpointOptions,
51
- NamedEndpointStats,
52
- Service,
53
- ServiceConfig,
54
- ServiceGroup,
55
- ServiceHandler,
56
- ServiceIdentity,
57
- ServiceInfo,
58
- ServiceMsg,
59
- ServiceStats,
60
- } from "./types";
61
-
62
- function validateName(context: string, name = "") {
63
- if (name === "") {
64
- throw Error(`${context} name required`);
65
- }
66
- const m = validName(name);
67
- if (m.length) {
68
- throw new Error(`invalid ${context} name - ${context} name ${m}`);
69
- }
70
- }
71
-
72
- function validName(name = ""): string {
73
- if (name === "") {
74
- throw Error(`name required`);
75
- }
76
- const RE = /^[-\w]+$/g;
77
- const m = name.match(RE);
78
- if (m === null) {
79
- for (const c of name.split("")) {
80
- const mm = c.match(RE);
81
- if (mm === null) {
82
- return `cannot contain '${c}'`;
83
- }
84
- }
85
- }
86
- return "";
87
- }
88
-
89
- /**
90
- * Services have common backplane subject pattern:
91
- *
92
- * `$SRV.PING|STATS|INFO` - pings or retrieves status for all services
93
- * `$SRV.PING|STATS|INFO.<name>` - pings or retrieves status for all services having the specified name
94
- * `$SRV.PING|STATS|INFO.<name>.<id>` - pings or retrieves status of a particular service
95
- *
96
- * Note that <name> and <id> are upper-cased.
97
- */
98
- export const ServiceApiPrefix = "$SRV";
99
-
100
- export class ServiceMsgImpl implements ServiceMsg {
101
- msg: Msg;
102
- constructor(msg: Msg) {
103
- this.msg = msg;
104
- }
105
-
106
- get data(): Uint8Array {
107
- return this.msg.data;
108
- }
109
-
110
- get sid(): number {
111
- return this.msg.sid;
112
- }
113
-
114
- get subject(): string {
115
- return this.msg.subject;
116
- }
117
-
118
- get reply(): string {
119
- return this.msg.reply || "";
120
- }
121
-
122
- get headers(): MsgHdrs | undefined {
123
- return this.msg.headers;
124
- }
125
-
126
- respond(data?: Payload, opts?: PublishOptions): boolean {
127
- return this.msg.respond(data, opts);
128
- }
129
-
130
- respondError(
131
- code: number,
132
- description: string,
133
- data?: Uint8Array,
134
- opts?: PublishOptions,
135
- ): boolean {
136
- opts = opts || {};
137
- opts.headers = opts.headers || headers();
138
- opts.headers?.set(ServiceErrorCodeHeader, `${code}`);
139
- opts.headers?.set(ServiceErrorHeader, description);
140
- return this.msg.respond(data, opts);
141
- }
142
-
143
- json<T = unknown>(reviver?: ReviverFn): T {
144
- return this.msg.json(reviver);
145
- }
146
-
147
- string(): string {
148
- return this.msg.string();
149
- }
150
- }
151
-
152
- export class ServiceGroupImpl implements ServiceGroup {
153
- subject: string;
154
- queue: string;
155
- srv: ServiceImpl;
156
- constructor(parent: ServiceGroup, name = "", queue = "") {
157
- if (name !== "") {
158
- validInternalToken("service group", name);
159
- }
160
- let root = "";
161
- if (parent instanceof ServiceImpl) {
162
- this.srv = parent as ServiceImpl;
163
- root = "";
164
- } else if (parent instanceof ServiceGroupImpl) {
165
- const sg = parent as ServiceGroupImpl;
166
- this.srv = sg.srv;
167
- if (queue === "" && sg.queue !== "") {
168
- queue = sg.queue;
169
- }
170
- root = sg.subject;
171
- } else {
172
- throw new Error("unknown ServiceGroup type");
173
- }
174
- this.subject = this.calcSubject(root, name);
175
- this.queue = queue;
176
- }
177
-
178
- calcSubject(root: string, name = ""): string {
179
- if (name === "") {
180
- return root;
181
- }
182
- return root !== "" ? `${root}.${name}` : name;
183
- }
184
- addEndpoint(
185
- name = "",
186
- opts?: ServiceHandler | EndpointOptions,
187
- ): QueuedIterator<ServiceMsg> {
188
- opts = opts || { subject: name } as EndpointOptions;
189
- const args: EndpointOptions = typeof opts === "function"
190
- ? { handler: opts, subject: name }
191
- : opts;
192
- validateName("endpoint", name);
193
- let { subject, handler, metadata, queue } = args;
194
- subject = subject || name;
195
- queue = queue || this.queue;
196
- validSubjectName("endpoint subject", subject);
197
- subject = this.calcSubject(this.subject, subject);
198
-
199
- const ne = { name, subject, queue, handler, metadata };
200
- return this.srv._addEndpoint(ne);
201
- }
202
-
203
- addGroup(name = "", queue = ""): ServiceGroup {
204
- return new ServiceGroupImpl(this, name, queue);
205
- }
206
- }
207
-
208
- function validSubjectName(context: string, subj: string) {
209
- if (subj === "") {
210
- throw new Error(`${context} cannot be empty`);
211
- }
212
- if (subj.indexOf(" ") !== -1) {
213
- throw new Error(`${context} cannot contain spaces: '${subj}'`);
214
- }
215
- const tokens = subj.split(".");
216
- tokens.forEach((v, idx) => {
217
- if (v === ">" && idx !== tokens.length - 1) {
218
- throw new Error(`${context} cannot have internal '>': '${subj}'`);
219
- }
220
- });
221
- }
222
-
223
- function validInternalToken(context: string, subj: string) {
224
- if (subj.indexOf(" ") !== -1) {
225
- throw new Error(`${context} cannot contain spaces: '${subj}'`);
226
- }
227
- const tokens = subj.split(".");
228
- tokens.forEach((v) => {
229
- if (v === ">") {
230
- throw new Error(`${context} name cannot contain internal '>': '${subj}'`);
231
- }
232
- });
233
- }
234
-
235
- type NamedEndpoint = {
236
- name: string;
237
- } & Endpoint;
238
-
239
- type ServiceSubscription<T = unknown> =
240
- & NamedEndpoint
241
- & {
242
- internal: boolean;
243
- sub: Sub<T>;
244
- qi?: QueuedIterator<T>;
245
- stats: NamedEndpointStatsImpl;
246
- metadata?: Record<string, string>;
247
- };
248
-
249
- export class ServiceImpl implements Service {
250
- nc: NatsConnection;
251
- _id: string;
252
- config: ServiceConfig;
253
- handlers: ServiceSubscription[];
254
- internal: ServiceSubscription[];
255
- _stopped: boolean;
256
- _done: Deferred<Error | null>;
257
- started: string;
258
-
259
- /**
260
- * @param verb
261
- * @param name
262
- * @param id
263
- * @param prefix - this is only supplied by tooling when building control subject that crosses an account
264
- */
265
- static controlSubject(
266
- verb: ServiceVerb,
267
- name = "",
268
- id = "",
269
- prefix?: string,
270
- ) {
271
- // the prefix is used as is, because it is an
272
- // account boundary permission
273
- const pre = prefix ?? ServiceApiPrefix;
274
- if (name === "" && id === "") {
275
- return `${pre}.${verb}`;
276
- }
277
- validateName("control subject name", name);
278
- if (id !== "") {
279
- validateName("control subject id", id);
280
- return `${pre}.${verb}.${name}.${id}`;
281
- }
282
- return `${pre}.${verb}.${name}`;
283
- }
284
-
285
- constructor(
286
- nc: NatsConnection,
287
- config: ServiceConfig = { name: "", version: "" },
288
- ) {
289
- this.nc = nc;
290
- this.config = Object.assign({}, config);
291
- if (!this.config.queue) {
292
- this.config.queue = "q";
293
- }
294
-
295
- // this will throw if no name
296
- validateName("name", this.config.name);
297
- validateName("queue", this.config.queue);
298
-
299
- // this will throw if not semver
300
- parseSemVer(this.config.version);
301
- this._id = nuid.next();
302
- this.internal = [] as ServiceSubscription[];
303
- this._done = deferred();
304
- this._stopped = false;
305
- this.handlers = [];
306
- this.started = new Date().toISOString();
307
- // initialize the stats
308
- this.reset();
309
-
310
- // close if the connection closes
311
- this.nc.closed()
312
- .then(() => {
313
- this.close().catch();
314
- })
315
- .catch((err) => {
316
- this.close(err).catch();
317
- });
318
- }
319
-
320
- get subjects(): string[] {
321
- return this.handlers.filter((s) => {
322
- return s.internal === false;
323
- }).map((s) => {
324
- return s.subject;
325
- });
326
- }
327
-
328
- get id(): string {
329
- return this._id;
330
- }
331
-
332
- get name(): string {
333
- return this.config.name;
334
- }
335
-
336
- get description(): string {
337
- return this.config.description ?? "";
338
- }
339
-
340
- get version(): string {
341
- return this.config.version;
342
- }
343
-
344
- get metadata(): Record<string, string> | undefined {
345
- return this.config.metadata;
346
- }
347
-
348
- errorToHeader(err: Error): MsgHdrs {
349
- const h = headers();
350
- if (err instanceof ServiceError) {
351
- const se = err as ServiceError;
352
- h.set(ServiceErrorHeader, se.message);
353
- h.set(ServiceErrorCodeHeader, `${se.code}`);
354
- } else {
355
- h.set(ServiceErrorHeader, err.message);
356
- h.set(ServiceErrorCodeHeader, "500");
357
- }
358
- return h;
359
- }
360
-
361
- setupHandler(
362
- h: NamedEndpoint,
363
- internal = false,
364
- ): ServiceSubscription {
365
- // internals don't use a queue
366
- const queue = internal ? "" : (h.queue ? h.queue : this.config.queue);
367
- const { name, subject, handler } = h as NamedEndpoint;
368
- const sv = h as ServiceSubscription;
369
- sv.internal = internal;
370
- if (internal) {
371
- this.internal.push(sv);
372
- }
373
- sv.stats = new NamedEndpointStatsImpl(name, subject, queue);
374
- sv.queue = queue;
375
-
376
- const callback = handler
377
- ? (err: NatsError | null, msg: Msg) => {
378
- if (err) {
379
- this.close(err);
380
- return;
381
- }
382
- const start = Date.now();
383
- try {
384
- handler(err, new ServiceMsgImpl(msg));
385
- } catch (err) {
386
- sv.stats.countError(err);
387
- msg?.respond(Empty, { headers: this.errorToHeader(err) });
388
- } finally {
389
- sv.stats.countLatency(start);
390
- }
391
- }
392
- : undefined;
393
-
394
- sv.sub = this.nc.subscribe(subject, {
395
- callback,
396
- queue,
397
- });
398
-
399
- sv.sub.closed
400
- .then(() => {
401
- if (!this._stopped) {
402
- this.close(new Error(`required subscription ${h.subject} stopped`))
403
- .catch();
404
- }
405
- })
406
- .catch((err) => {
407
- if (!this._stopped) {
408
- const ne = new Error(
409
- `required subscription ${h.subject} errored: ${err.message}`,
410
- );
411
- ne.stack = err.stack;
412
- this.close(ne).catch();
413
- }
414
- });
415
- return sv;
416
- }
417
-
418
- info(): ServiceInfo {
419
- return {
420
- type: ServiceResponseType.INFO,
421
- name: this.name,
422
- id: this.id,
423
- version: this.version,
424
- description: this.description,
425
- metadata: this.metadata,
426
- endpoints: this.endpoints(),
427
- } as ServiceInfo;
428
- }
429
-
430
- endpoints(): EndpointInfo[] {
431
- return this.handlers.map((v) => {
432
- const { subject, metadata, name, queue } = v;
433
- return { subject, metadata, name, queue_group: queue };
434
- });
435
- }
436
-
437
- async stats(): Promise<ServiceStats> {
438
- const endpoints: NamedEndpointStats[] = [];
439
- for (const h of this.handlers) {
440
- if (typeof this.config.statsHandler === "function") {
441
- try {
442
- h.stats.data = await this.config.statsHandler(h);
443
- } catch (err) {
444
- h.stats.countError(err);
445
- }
446
- }
447
- endpoints.push(h.stats.stats(h.qi));
448
- }
449
- return {
450
- type: ServiceResponseType.STATS,
451
- name: this.name,
452
- id: this.id,
453
- version: this.version,
454
- started: this.started,
455
- metadata: this.metadata,
456
- endpoints,
457
- };
458
- }
459
-
460
- addInternalHandler(
461
- verb: ServiceVerb,
462
- handler: (err: NatsError | null, msg: Msg) => Promise<void>,
463
- ) {
464
- const v = `${verb}`.toUpperCase();
465
- this._doAddInternalHandler(`${v}-all`, verb, handler);
466
- this._doAddInternalHandler(`${v}-kind`, verb, handler, this.name);
467
- this._doAddInternalHandler(
468
- `${v}`,
469
- verb,
470
- handler,
471
- this.name,
472
- this.id,
473
- );
474
- }
475
-
476
- _doAddInternalHandler(
477
- name: string,
478
- verb: ServiceVerb,
479
- handler: (err: NatsError | null, msg: Msg) => Promise<void>,
480
- kind = "",
481
- id = "",
482
- ) {
483
- const endpoint = {} as NamedEndpoint;
484
- endpoint.name = name;
485
- endpoint.subject = ServiceImpl.controlSubject(verb, kind, id);
486
- endpoint.handler = handler;
487
- this.setupHandler(endpoint, true);
488
- }
489
-
490
- start(): Promise<Service> {
491
- const jc = JSONCodec();
492
- const statsHandler = (err: Error | null, msg: Msg): Promise<void> => {
493
- if (err) {
494
- this.close(err);
495
- return Promise.reject(err);
496
- }
497
- return this.stats().then((s) => {
498
- msg?.respond(jc.encode(s));
499
- return Promise.resolve();
500
- });
501
- };
502
-
503
- const infoHandler = (err: Error | null, msg: Msg): Promise<void> => {
504
- if (err) {
505
- this.close(err);
506
- return Promise.reject(err);
507
- }
508
- msg?.respond(jc.encode(this.info()));
509
- return Promise.resolve();
510
- };
511
-
512
- const ping = jc.encode(this.ping());
513
- const pingHandler = (err: Error | null, msg: Msg): Promise<void> => {
514
- if (err) {
515
- this.close(err).then().catch();
516
- return Promise.reject(err);
517
- }
518
- msg.respond(ping);
519
- return Promise.resolve();
520
- };
521
-
522
- this.addInternalHandler(ServiceVerb.PING, pingHandler);
523
- this.addInternalHandler(ServiceVerb.STATS, statsHandler);
524
- this.addInternalHandler(ServiceVerb.INFO, infoHandler);
525
-
526
- // now the actual service
527
- this.handlers.forEach((h) => {
528
- const { subject } = h as Endpoint;
529
- if (typeof subject !== "string") {
530
- return;
531
- }
532
- // this is expected in cases where main subject is just
533
- // a root subject for multiple endpoints - user can disable
534
- // listening to the root endpoint, by specifying null
535
- if (h.handler === null) {
536
- return;
537
- }
538
- this.setupHandler(h as unknown as NamedEndpoint);
539
- });
540
-
541
- return Promise.resolve(this);
542
- }
543
-
544
- close(err?: Error): Promise<null | Error> {
545
- if (this._stopped) {
546
- return this._done;
547
- }
548
- this._stopped = true;
549
- let buf: Promise<void>[] = [];
550
- if (!this.nc.isClosed()) {
551
- buf = this.handlers.concat(this.internal).map((h) => {
552
- return h.sub.drain();
553
- });
554
- }
555
- Promise.allSettled(buf)
556
- .then(() => {
557
- this._done.resolve(err ? err : null);
558
- });
559
- return this._done;
560
- }
561
-
562
- get stopped(): Promise<null | Error> {
563
- return this._done;
564
- }
565
-
566
- get isStopped(): boolean {
567
- return this._stopped;
568
- }
569
-
570
- stop(err?: Error): Promise<null | Error> {
571
- return this.close(err);
572
- }
573
-
574
- ping(): ServiceIdentity {
575
- return {
576
- type: ServiceResponseType.PING,
577
- name: this.name,
578
- id: this.id,
579
- version: this.version,
580
- metadata: this.metadata,
581
- };
582
- }
583
-
584
- reset(): void {
585
- // pretend we restarted
586
- this.started = new Date().toISOString();
587
- if (this.handlers) {
588
- for (const h of this.handlers) {
589
- h.stats.reset(h.qi);
590
- }
591
- }
592
- }
593
-
594
- addGroup(name: string, queue?: string): ServiceGroup {
595
- return new ServiceGroupImpl(this, name, queue);
596
- }
597
-
598
- addEndpoint(
599
- name: string,
600
- handler?: ServiceHandler | EndpointOptions,
601
- ): QueuedIterator<ServiceMsg> {
602
- const sg = new ServiceGroupImpl(this);
603
- return sg.addEndpoint(name, handler);
604
- }
605
-
606
- _addEndpoint(
607
- e: NamedEndpoint,
608
- ): QueuedIterator<ServiceMsg> {
609
- const qi = new QueuedIteratorImpl<ServiceMsg>();
610
- qi.noIterator = typeof e.handler === "function";
611
- if (!qi.noIterator) {
612
- e.handler = (err, msg): void => {
613
- err ? this.stop(err).catch() : qi.push(new ServiceMsgImpl(msg));
614
- };
615
- // close the service if the iterator closes
616
- qi.iterClosed.then(() => {
617
- this.close().catch();
618
- });
619
- }
620
- // track the iterator for stats
621
- const ss = this.setupHandler(e, false);
622
- ss.qi = qi;
623
- this.handlers.push(ss);
624
- return qi;
625
- }
626
- }
627
-
628
- class NamedEndpointStatsImpl implements NamedEndpointStats {
629
- name: string;
630
- subject: string;
631
- average_processing_time: Nanos;
632
- num_requests: number;
633
- processing_time: Nanos;
634
- num_errors: number;
635
- last_error?: string;
636
- data?: unknown;
637
- metadata?: Record<string, string>;
638
- queue: string;
639
-
640
- constructor(name: string, subject: string, queue = "") {
641
- this.name = name;
642
- this.subject = subject;
643
- this.average_processing_time = 0;
644
- this.num_errors = 0;
645
- this.num_requests = 0;
646
- this.processing_time = 0;
647
- this.queue = queue;
648
- }
649
- reset(qi?: QueuedIterator<unknown>) {
650
- this.num_requests = 0;
651
- this.processing_time = 0;
652
- this.average_processing_time = 0;
653
- this.num_errors = 0;
654
- this.last_error = undefined;
655
- this.data = undefined;
656
- const qii = qi as QueuedIteratorImpl<unknown>;
657
- if (qii) {
658
- qii.time = 0;
659
- qii.processed = 0;
660
- }
661
- }
662
- countLatency(start: number) {
663
- this.num_requests++;
664
- this.processing_time += nanos(Date.now() - start);
665
- this.average_processing_time = Math.round(
666
- this.processing_time / this.num_requests,
667
- );
668
- }
669
- countError(err: Error): void {
670
- this.num_errors++;
671
- this.last_error = err.message;
672
- }
673
-
674
- _stats(): NamedEndpointStats {
675
- const {
676
- name,
677
- subject,
678
- average_processing_time,
679
- num_errors,
680
- num_requests,
681
- processing_time,
682
- last_error,
683
- data,
684
- queue,
685
- } = this;
686
- return {
687
- name,
688
- subject,
689
- average_processing_time,
690
- num_errors,
691
- num_requests,
692
- processing_time,
693
- last_error,
694
- data,
695
- queue_group: queue,
696
- };
697
- }
698
-
699
- stats(qi?: QueuedIterator<unknown>): NamedEndpointStats {
700
- const qii = qi as QueuedIteratorImpl<unknown>;
701
- if (qii?.noIterator === false) {
702
- // grab stats in the iterator
703
- this.processing_time = qii.time;
704
- this.num_requests = qii.processed;
705
- this.average_processing_time =
706
- this.processing_time > 0 && this.num_requests > 0
707
- ? this.processing_time / this.num_requests
708
- : 0;
709
- }
710
- return this._stats();
711
- }
712
- }
@@ -1,106 +0,0 @@
1
- /*
2
- * Copyright 2022-2024 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
- import {
16
- Empty,
17
- JSONCodec,
18
- QueuedIteratorImpl,
19
- RequestStrategy,
20
- } from "@nats-io/nats-core/internal";
21
-
22
- import type {
23
- NatsConnection,
24
- QueuedIterator,
25
- RequestManyOptions,
26
- } from "@nats-io/nats-core/internal";
27
-
28
- import { ServiceImpl } from "./service";
29
- import { ServiceVerb } from "./types";
30
-
31
- import type {
32
- ServiceClient,
33
- ServiceIdentity,
34
- ServiceInfo,
35
- ServiceStats,
36
- } from "./types";
37
-
38
- export class ServiceClientImpl implements ServiceClient {
39
- nc: NatsConnection;
40
- prefix: string | undefined;
41
- opts: RequestManyOptions;
42
- constructor(
43
- nc: NatsConnection,
44
- opts: RequestManyOptions = {
45
- strategy: RequestStrategy.JitterTimer,
46
- maxWait: 2000,
47
- },
48
- prefix?: string,
49
- ) {
50
- this.nc = nc;
51
- this.prefix = prefix;
52
- this.opts = opts;
53
- }
54
-
55
- ping(
56
- name = "",
57
- id = "",
58
- ): Promise<QueuedIterator<ServiceIdentity>> {
59
- return this.q<ServiceIdentity>(ServiceVerb.PING, name, id);
60
- }
61
-
62
- stats(
63
- name = "",
64
- id = "",
65
- ): Promise<QueuedIterator<ServiceStats>> {
66
- return this.q<ServiceStats>(ServiceVerb.STATS, name, id);
67
- }
68
-
69
- info(
70
- name = "",
71
- id = "",
72
- ): Promise<QueuedIterator<ServiceInfo>> {
73
- return this.q<ServiceInfo>(ServiceVerb.INFO, name, id);
74
- }
75
-
76
- async q<T>(
77
- v: ServiceVerb,
78
- name = "",
79
- id = "",
80
- ): Promise<QueuedIterator<T>> {
81
- const iter = new QueuedIteratorImpl<T>();
82
- const jc = JSONCodec<T>();
83
- const subj = ServiceImpl.controlSubject(v, name, id, this.prefix);
84
- const responses = await this.nc.requestMany(subj, Empty, this.opts);
85
- (async () => {
86
- for await (const m of responses) {
87
- try {
88
- const s = jc.decode(m.data);
89
- iter.push(s);
90
- } catch (err) {
91
- // @ts-ignore: pushing fn
92
- iter.push(() => {
93
- iter.stop(err);
94
- });
95
- }
96
- }
97
- //@ts-ignore: push a fn
98
- iter.push(() => {
99
- iter.stop();
100
- });
101
- })().catch((err) => {
102
- iter.stop(err);
103
- });
104
- return iter;
105
- }
106
- }
@@ -1,300 +0,0 @@
1
- import type {
2
- Msg,
3
- Nanos,
4
- NatsError,
5
- PublishOptions,
6
- QueuedIterator,
7
- } from "@nats-io/nats-core";
8
-
9
- export interface ServiceMsg extends Msg {
10
- respondError(
11
- code: number,
12
- description: string,
13
- data?: Uint8Array,
14
- opts?: PublishOptions,
15
- ): boolean;
16
- }
17
-
18
- export type ServiceHandler = (err: NatsError | null, msg: ServiceMsg) => void;
19
- /**
20
- * A service Endpoint
21
- */
22
- export type Endpoint = {
23
- /**
24
- * Subject where the endpoint listens
25
- */
26
- subject: string;
27
- /**
28
- * An optional handler - if not set the service is an iterator
29
- * @param err
30
- * @param msg
31
- */
32
- handler?: ServiceHandler;
33
- /**
34
- * Optional metadata about the endpoint
35
- */
36
- metadata?: Record<string, string>;
37
- /**
38
- * Optional queue group to run this particular endpoint in. The service's configuration
39
- * queue configuration will be used. See {@link ServiceConfig}.
40
- */
41
- queue?: string;
42
- };
43
- export type EndpointOptions = Partial<Endpoint>;
44
- export type EndpointInfo = {
45
- name: string;
46
- subject: string;
47
- metadata?: Record<string, string>;
48
- queue_group?: string;
49
- };
50
-
51
- export interface ServiceGroup {
52
- /**
53
- * The name of the endpoint must be a simple subject token with no wildcards
54
- * @param name
55
- * @param opts is either a handler or a more complex options which allows a
56
- * subject, handler, and/or schema
57
- */
58
- addEndpoint(
59
- name: string,
60
- opts?: ServiceHandler | EndpointOptions,
61
- ): QueuedIterator<ServiceMsg>;
62
-
63
- /**
64
- * A group is a subject prefix from which endpoints can be added.
65
- * Can be empty to allow for prefixes or tokens that are set at runtime
66
- * without requiring editing of the service.
67
- * Note that an optional queue can be specified, all endpoints added to
68
- * the group, will use the specified queue unless the endpoint overrides it.
69
- * see {@link EndpointOptions} and {@link ServiceConfig}.
70
- * @param subject
71
- * @param queue
72
- */
73
- addGroup(subject?: string, queue?: string): ServiceGroup;
74
- }
75
-
76
- export type ServiceMetadata = {
77
- metadata?: Record<string, string>;
78
- };
79
-
80
- export enum ServiceResponseType {
81
- STATS = "io.nats.micro.v1.stats_response",
82
- INFO = "io.nats.micro.v1.info_response",
83
- PING = "io.nats.micro.v1.ping_response",
84
- }
85
-
86
- export interface ServiceResponse {
87
- /**
88
- * Response type schema
89
- */
90
- type: ServiceResponseType;
91
- }
92
-
93
- export type ServiceIdentity = ServiceResponse & ServiceMetadata & {
94
- /**
95
- * The kind of the service reporting the stats
96
- */
97
- name: string;
98
- /**
99
- * The unique ID of the service reporting the stats
100
- */
101
- id: string;
102
- /**
103
- * A version for the service
104
- */
105
- version: string;
106
- };
107
- export type NamedEndpointStats = {
108
- /**
109
- * The name of the endpoint
110
- */
111
- name: string;
112
- /**
113
- * The subject the endpoint is listening on
114
- */
115
- subject: string;
116
- /**
117
- * The number of requests received by the endpoint
118
- */
119
- num_requests: number;
120
- /**
121
- * Number of errors that the endpoint has raised
122
- */
123
- num_errors: number;
124
- /**
125
- * If set, the last error triggered by the endpoint
126
- */
127
- last_error?: string;
128
- /**
129
- * A field that can be customized with any data as returned by stats handler see {@link ServiceConfig}
130
- */
131
- data?: unknown;
132
- /**
133
- * Total processing_time for the service
134
- */
135
- processing_time: Nanos;
136
- /**
137
- * Average processing_time is the total processing_time divided by the num_requests
138
- */
139
- average_processing_time: Nanos;
140
- /**
141
- * The queue group the endpoint is listening on
142
- */
143
- queue_group?: string;
144
- };
145
- /**
146
- * Statistics for an endpoint
147
- */
148
- export type EndpointStats = ServiceIdentity & {
149
- endpoints?: NamedEndpointStats[];
150
- /**
151
- * ISO Date string when the service started
152
- */
153
- started: string;
154
- };
155
- export type ServiceInfo = ServiceIdentity & {
156
- /**
157
- * Description for the service
158
- */
159
- description: string;
160
- /**
161
- * Service metadata
162
- */
163
- metadata?: Record<string, string>;
164
- /**
165
- * Information about the Endpoints
166
- */
167
- endpoints: EndpointInfo[];
168
- };
169
- export type ServiceConfig = {
170
- /**
171
- * A type for a service
172
- */
173
- name: string;
174
- /**
175
- * A version identifier for the service
176
- */
177
- version: string;
178
- /**
179
- * Description for the service
180
- */
181
- description?: string;
182
- /**
183
- * A customized handler for the stats of an endpoint. The
184
- * data returned by the endpoint will be serialized as is
185
- * @param endpoint
186
- */
187
- statsHandler?: (
188
- endpoint: Endpoint,
189
- ) => Promise<unknown | null>;
190
-
191
- /**
192
- * Optional metadata about the service
193
- */
194
- metadata?: Record<string, string>;
195
- /**
196
- * Optional queue group to run the service in. By default,
197
- * then queue name is "q". Note that this configuration will
198
- * be the default for all endpoints and groups.
199
- */
200
- queue?: string;
201
- };
202
- /**
203
- * The stats of a service
204
- */
205
- export type ServiceStats = ServiceIdentity & EndpointStats;
206
-
207
- export interface Service extends ServiceGroup {
208
- /**
209
- * A promise that gets resolved to null or Error once the service ends.
210
- * If an error, then service exited because of an error.
211
- */
212
- stopped: Promise<null | Error>;
213
- /**
214
- * True if the service is stopped
215
- */
216
- // FIXME: this would be better as stop - but the queued iterator may be an issue perhaps call it `isStopped`
217
- isStopped: boolean;
218
-
219
- /**
220
- * Returns the stats for the service.
221
- */
222
- stats(): Promise<ServiceStats>;
223
-
224
- /**
225
- * Returns a service info for the service
226
- */
227
- info(): ServiceInfo;
228
-
229
- /**
230
- * Returns the identity used by this service
231
- */
232
- ping(): ServiceIdentity;
233
-
234
- /**
235
- * Resets all the stats
236
- */
237
- reset(): void;
238
-
239
- /**
240
- * Stop the service returning a promise once the service completes.
241
- * If the service was stopped due to an error, that promise resolves to
242
- * the specified error
243
- */
244
- stop(err?: Error): Promise<null | Error>;
245
- }
246
-
247
- export const ServiceErrorHeader = "Nats-Service-Error";
248
- export const ServiceErrorCodeHeader = "Nats-Service-Error-Code";
249
-
250
- export class ServiceError extends Error {
251
- code: number;
252
-
253
- constructor(code: number, message: string) {
254
- super(message);
255
- this.code = code;
256
- }
257
-
258
- static isServiceError(msg: Msg): boolean {
259
- return ServiceError.toServiceError(msg) !== null;
260
- }
261
-
262
- static toServiceError(msg: Msg): ServiceError | null {
263
- const scode = msg?.headers?.get(ServiceErrorCodeHeader) || "";
264
- if (scode !== "") {
265
- const code = parseInt(scode) || 400;
266
- const description = msg?.headers?.get(ServiceErrorHeader) || "";
267
- return new ServiceError(code, description.length ? description : scode);
268
- }
269
- return null;
270
- }
271
- }
272
-
273
- export enum ServiceVerb {
274
- PING = "PING",
275
- STATS = "STATS",
276
- INFO = "INFO",
277
- }
278
-
279
- export interface ServiceClient {
280
- /**
281
- * Pings services
282
- * @param name - optional
283
- * @param id - optional
284
- */
285
- ping(name?: string, id?: string): Promise<QueuedIterator<ServiceIdentity>>;
286
-
287
- /**
288
- * Requests all the stats from services
289
- * @param name
290
- * @param id
291
- */
292
- stats(name?: string, id?: string): Promise<QueuedIterator<ServiceStats>>;
293
-
294
- /**
295
- * Requests info from services
296
- * @param name
297
- * @param id
298
- */
299
- info(name?: string, id?: string): Promise<QueuedIterator<ServiceInfo>>;
300
- }