-
Notifications
You must be signed in to change notification settings - Fork 51
/
02_multiple_endpoints.ts
166 lines (152 loc) · 4.75 KB
/
02_multiple_endpoints.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
/*
* Copyright 2022-2023 The NATS Authors
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { connect, JSONCodec, ServiceError, ServiceMsg } from "../../src/mod.ts";
const jc = JSONCodec();
// connect to NATS on demo.nats.io
const nc = await connect({ servers: ["demo.nats.io"] });
// create a service - using the statsHandler and decoder
const calc = await nc.services.add({
name: "calc",
version: "0.0.1",
description: "example calculator service",
metadata: {
"example": "entry",
},
});
// For this example the thing we want to showcase is how you can
// create service that has multiple endpoints.
// The service will have `sum`, `max`, `average` and `min` operations.
// While we could create a service that listens on `sum`, `max`, etc.,
// creating a complex hierarchy will allow you to carve the subject
// name space to allow for better access control, and organize your
// services better.
// In the service API, you `addGroup()` to the service, which will
// introduce a prefix for the calculator's endpoints. The group name
// can be any valid subject that can be prefixed into another.
const g = calc.addGroup("calc");
// We can now add endpoints under this which will augment the subject
// space, adding an endpoint. Endpoints can only have a simple name
// and can specify an optional callback:
// this is the simplest endpoint - returns an iterator
// additional options such as a handler, subject, or schema can be
// specified.
// this endpoint is accessible as `calc.sum`
const sums = g.addEndpoint("sum", {
metadata: {
"input": "JSON number array",
"output": "JSON number with the sum",
},
});
(async () => {
for await (const m of sums) {
const numbers = decode(m);
const s = numbers.reduce((sum, v) => {
return sum + v;
});
m.respond(jc.encode(s));
}
})().then();
// Here's another implemented using a callback, will be accessible by `calc.average`:
g.addEndpoint("average", {
handler: (err, m) => {
if (err) {
calc.stop(err);
return;
}
const numbers = decode(m);
const sum = numbers.reduce((sum, v) => {
return sum + v;
});
m.respond(jc.encode(sum / numbers.length));
},
metadata: {
"input": "JSON number array",
"output": "JSON number average value found in the array",
},
});
// and another using a callback, and specifying our schema:
g.addEndpoint("min", {
handler: (err, m) => {
if (err) {
calc.stop(err);
return;
}
const numbers = decode(m);
const min = numbers.reduce((n, v) => {
return Math.min(n, v);
});
m.respond(jc.encode(min));
},
metadata: {
"input": "JSON number array",
"output": "JSON number min value found in the array",
},
});
g.addEndpoint("max", {
handler: (err, m) => {
if (err) {
calc.stop(err);
return;
}
const numbers = decode(m);
const max = numbers.reduce((n, v) => {
return Math.max(n, v);
});
m.respond(jc.encode(max));
},
metadata: {
"input": "JSON number array",
"output": "JSON number max value found in the array",
},
});
calc.stopped.then((err: Error | null) => {
console.log(`calc stopped ${err ? "because: " + err.message : ""}`);
});
// Now we switch gears and look at a client making a request
async function calculate(op: string, a: number[]): Promise<void> {
const r = await nc.request(`calc.${op}`, jc.encode(a));
if (ServiceError.isServiceError(r)) {
console.log(ServiceError.toServiceError(r));
return;
}
const ans = r.json<number>();
console.log(`${op} ${a.join(", ")} = ${ans}`);
}
await Promise.all([
calculate("sum", [5, 10, 15]),
calculate("average", [5, 10, 15]),
calculate("min", [5, 10, 15]),
calculate("max", [5, 10, 15]),
]);
// stop the service
await calc.stop();
// and close the connection
await nc.close();
// a simple decoder that tosses a ServiceError if the input is not what we want.
function decode(m: ServiceMsg): number[] {
const a = m.json<number[]>();
if (!Array.isArray(a)) {
throw new ServiceError(400, "input requires array");
}
if (a.length === 0) {
throw new ServiceError(400, "array must have at least one number");
}
a.forEach((v) => {
if (typeof v !== "number") {
throw new ServiceError(400, "array elements must be numbers");
}
});
return a;
}