pepr 0.13.0 → 0.13.2

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.
Files changed (51) hide show
  1. package/dist/cli.js +54 -28
  2. package/dist/controller.js +1 -1
  3. package/dist/lib/assets/index.d.ts +5 -6
  4. package/dist/lib/assets/index.d.ts.map +1 -1
  5. package/dist/lib/assets/loader.d.ts +2 -8
  6. package/dist/lib/assets/loader.d.ts.map +1 -1
  7. package/dist/lib/capability.d.ts +4 -7
  8. package/dist/lib/capability.d.ts.map +1 -1
  9. package/dist/lib/controller.d.ts +3 -49
  10. package/dist/lib/controller.d.ts.map +1 -1
  11. package/dist/lib/errors.d.ts +12 -0
  12. package/dist/lib/errors.d.ts.map +1 -0
  13. package/dist/lib/k8s/types.d.ts +5 -1
  14. package/dist/lib/k8s/types.d.ts.map +1 -1
  15. package/dist/lib/metrics.d.ts +6 -12
  16. package/dist/lib/metrics.d.ts.map +1 -1
  17. package/dist/lib/module.d.ts +2 -2
  18. package/dist/lib/module.d.ts.map +1 -1
  19. package/dist/lib/mutate-processor.d.ts.map +1 -1
  20. package/dist/lib/mutate-request.d.ts +2 -2
  21. package/dist/lib/mutate-request.d.ts.map +1 -1
  22. package/dist/lib/types.d.ts +3 -9
  23. package/dist/lib/types.d.ts.map +1 -1
  24. package/dist/lib/validate-processor.d.ts.map +1 -1
  25. package/dist/lib/validate-request.d.ts +2 -2
  26. package/dist/lib/validate-request.d.ts.map +1 -1
  27. package/dist/lib.js +247 -191
  28. package/dist/lib.js.map +3 -3
  29. package/jest.config.json +4 -0
  30. package/journey/before.ts +21 -0
  31. package/journey/k8s.ts +81 -0
  32. package/journey/pepr-build.ts +69 -0
  33. package/journey/pepr-deploy.ts +133 -0
  34. package/journey/pepr-dev.ts +155 -0
  35. package/journey/pepr-format.ts +13 -0
  36. package/journey/pepr-init.ts +12 -0
  37. package/package.json +20 -21
  38. package/src/lib/assets/index.ts +15 -8
  39. package/src/lib/assets/loader.ts +4 -12
  40. package/src/lib/assets/webhooks.ts +2 -2
  41. package/src/lib/capability.ts +34 -32
  42. package/src/lib/controller.ts +111 -93
  43. package/src/lib/errors.ts +20 -0
  44. package/src/lib/k8s/types.ts +5 -1
  45. package/src/lib/metrics.ts +45 -32
  46. package/src/lib/module.ts +24 -10
  47. package/src/lib/mutate-processor.ts +5 -3
  48. package/src/lib/mutate-request.ts +22 -9
  49. package/src/lib/types.ts +4 -10
  50. package/src/lib/validate-processor.ts +8 -0
  51. package/src/lib/validate-request.ts +19 -7
@@ -11,6 +11,7 @@ import {
11
11
  BindingFilter,
12
12
  BindingWithName,
13
13
  CapabilityCfg,
14
+ CapabilityExport,
14
15
  Event,
15
16
  GenericClass,
16
17
  MutateAction,
@@ -22,35 +23,37 @@ import {
22
23
  /**
23
24
  * A capability is a unit of functionality that can be registered with the Pepr runtime.
24
25
  */
25
- export class Capability implements CapabilityCfg {
26
- private _name: string;
27
- private _description: string;
28
- private _namespaces?: string[] | undefined;
29
-
30
- private _bindings: Binding[] = [];
31
-
32
- get bindings(): Binding[] {
33
- return this._bindings;
26
+ export class Capability implements CapabilityExport {
27
+ #name: string;
28
+ #description: string;
29
+ #namespaces?: string[] | undefined;
30
+ #bindings: Binding[] = [];
31
+
32
+ get bindings() {
33
+ return this.#bindings;
34
34
  }
35
35
 
36
36
  get name() {
37
- return this._name;
37
+ return this.#name;
38
38
  }
39
39
 
40
40
  get description() {
41
- return this._description;
41
+ return this.#description;
42
42
  }
43
43
 
44
44
  get namespaces() {
45
- return this._namespaces || [];
45
+ return this.#namespaces || [];
46
46
  }
47
47
 
48
48
  constructor(cfg: CapabilityCfg) {
49
- this._name = cfg.name;
50
- this._description = cfg.description;
51
- this._namespaces = cfg.namespaces;
49
+ this.#name = cfg.name;
50
+ this.#description = cfg.description;
51
+ this.#namespaces = cfg.namespaces;
52
52
 
53
- Log.info(`Capability ${this._name} registered`);
53
+ // Bind When() to this instance
54
+ this.When = this.When.bind(this);
55
+
56
+ Log.info(`Capability ${this.#name} registered`);
54
57
  Log.debug(cfg);
55
58
  }
56
59
 
@@ -63,7 +66,7 @@ export class Capability implements CapabilityCfg {
63
66
  * @param kind if using a custom KubernetesObject not available in `a.*`, specify the GroupVersionKind
64
67
  * @returns
65
68
  */
66
- When = <T extends GenericClass>(model: T, kind?: GroupVersionKind): WhenSelector<T> => {
69
+ When<T extends GenericClass>(model: T, kind?: GroupVersionKind): WhenSelector<T> {
67
70
  const matchedKind = modelToGroupVersionKind(model.name);
68
71
 
69
72
  // If the kind is not specified and the model is not a KubernetesObject, throw an error
@@ -83,8 +86,9 @@ export class Capability implements CapabilityCfg {
83
86
  },
84
87
  };
85
88
 
86
- const prefix = `${this._name}: ${model.name}`;
87
-
89
+ const bindings = this.#bindings;
90
+ const prefix = `${this.#name}: ${model.name}`;
91
+ const commonChain = { WithLabel, WithAnnotation, Mutate, Validate };
88
92
  const isNotEmpty = (value: object) => Object.keys(value).length > 0;
89
93
  const log = (message: string, cbString: string) => {
90
94
  const filteredObj = pickBy(isNotEmpty, binding.filters);
@@ -94,27 +98,27 @@ export class Capability implements CapabilityCfg {
94
98
  Log.debug(cbString, prefix);
95
99
  };
96
100
 
97
- const Validate = (validateCallback: ValidateAction<T>): void => {
101
+ function Validate(validateCallback: ValidateAction<T>): void {
98
102
  if (!isWatchMode) {
99
103
  log("Validate Action", validateCallback.toString());
100
104
 
101
105
  // Push the binding to the list of bindings for this capability as a new BindingAction
102
106
  // with the callback function to preserve
103
- this._bindings.push({
107
+ bindings.push({
104
108
  ...binding,
105
109
  isValidate: true,
106
110
  validateCallback,
107
111
  });
108
112
  }
109
- };
113
+ }
110
114
 
111
- const Mutate = (mutateCallback: MutateAction<T>): MutateActionChain<T> => {
115
+ function Mutate(mutateCallback: MutateAction<T>): MutateActionChain<T> {
112
116
  if (!isWatchMode) {
113
117
  log("Mutate Action", mutateCallback.toString());
114
118
 
115
119
  // Push the binding to the list of bindings for this capability as a new BindingAction
116
120
  // with the callback function to preserve
117
- this._bindings.push({
121
+ bindings.push({
118
122
  ...binding,
119
123
  isMutate: true,
120
124
  mutateCallback,
@@ -123,7 +127,7 @@ export class Capability implements CapabilityCfg {
123
127
 
124
128
  // Now only allow adding actions to the same binding
125
129
  return { Validate };
126
- };
130
+ }
127
131
 
128
132
  function InNamespace(...namespaces: string[]): BindingWithName<T> {
129
133
  Log.debug(`Add namespaces filter ${namespaces}`, prefix);
@@ -143,22 +147,20 @@ export class Capability implements CapabilityCfg {
143
147
  return commonChain;
144
148
  }
145
149
 
146
- const WithAnnotation = (key: string, value = ""): BindingFilter<T> => {
150
+ function WithAnnotation(key: string, value = ""): BindingFilter<T> {
147
151
  Log.debug(`Add annotation filter ${key}=${value}`, prefix);
148
152
  binding.filters.annotations[key] = value;
149
153
  return commonChain;
150
- };
151
-
152
- const commonChain = { WithLabel, WithAnnotation, Mutate, Validate };
154
+ }
153
155
 
154
- const bindEvent = (event: Event) => {
156
+ function bindEvent(event: Event) {
155
157
  binding.event = event;
156
158
  return {
157
159
  ...commonChain,
158
160
  InNamespace,
159
161
  WithName,
160
162
  };
161
- };
163
+ }
162
164
 
163
165
  return {
164
166
  IsCreatedOrUpdated: () => bindEvent(Event.CreateOrUpdate),
@@ -166,5 +168,5 @@ export class Capability implements CapabilityCfg {
166
168
  IsUpdated: () => bindEvent(Event.Update),
167
169
  IsDeleted: () => bindEvent(Event.Delete),
168
170
  };
169
- };
171
+ }
170
172
  }
@@ -15,61 +15,59 @@ import { ModuleConfig } from "./types";
15
15
  import { validateProcessor } from "./validate-processor";
16
16
 
17
17
  export class Controller {
18
- private readonly _app = express();
19
- private _running = false;
20
- private metricsCollector = new MetricsCollector("pepr");
18
+ // Track whether the server is running
19
+ #running = false;
20
+
21
+ // Metrics collector
22
+ #metricsCollector = new MetricsCollector("pepr");
21
23
 
22
24
  // The token used to authenticate requests
23
- private _token = "";
25
+ #token = "";
26
+
27
+ // The express app instance
28
+ readonly #app = express();
29
+
30
+ // Initialized with the constructor
31
+ readonly #config: ModuleConfig;
32
+ readonly #capabilities: Capability[];
33
+ readonly #beforeHook?: (req: Request) => void;
34
+ readonly #afterHook?: (res: MutateResponse) => void;
24
35
 
25
36
  constructor(
26
- private readonly _config: ModuleConfig,
27
- private readonly _capabilities: Capability[],
28
- private readonly _beforeHook?: (req: Request) => void,
29
- private readonly _afterHook?: (res: MutateResponse) => void,
37
+ config: ModuleConfig,
38
+ capabilities: Capability[],
39
+ beforeHook?: (req: Request) => void,
40
+ afterHook?: (res: MutateResponse) => void,
30
41
  ) {
42
+ this.#config = config;
43
+ this.#capabilities = capabilities;
44
+ this.#beforeHook = beforeHook;
45
+ this.#afterHook = afterHook;
46
+
47
+ // Bind public methods
48
+ this.startServer = this.startServer.bind(this);
49
+
31
50
  // Middleware for logging requests
32
- this._app.use(this.logger);
51
+ this.#app.use(Controller.#logger);
33
52
 
34
53
  // Middleware for parsing JSON, limit to 2mb vs 100K for K8s compatibility
35
- this._app.use(express.json({ limit: "2mb" }));
54
+ this.#app.use(express.json({ limit: "2mb" }));
36
55
 
37
- if (_beforeHook) {
38
- Log.info(`Using beforeHook: ${_beforeHook}`);
56
+ if (beforeHook) {
57
+ Log.info(`Using beforeHook: ${beforeHook}`);
39
58
  }
40
59
 
41
- if (_afterHook) {
42
- Log.info(`Using afterHook: ${_afterHook}`);
60
+ if (afterHook) {
61
+ Log.info(`Using afterHook: ${afterHook}`);
43
62
  }
44
63
 
45
64
  // Bind endpoints
46
- this.bindEndpoints();
65
+ this.#bindEndpoints();
47
66
  }
48
67
 
49
- private bindEndpoints = () => {
50
- // Health check endpoint
51
- this._app.get("/healthz", this.healthz);
52
-
53
- // Metrics endpoint
54
- this._app.get("/metrics", this.metrics);
55
-
56
- if (isWatchMode) {
57
- return;
58
- }
59
-
60
- // Require auth for webhook endpoints
61
- this._app.use(["/mutate/:token", "/validate/:token"], this.validateToken);
62
-
63
- // Mutate endpoint
64
- this._app.post("/mutate/:token", this.admissionReq("Mutate"));
65
-
66
- // Validate endpoint
67
- this._app.post("/validate/:token", this.admissionReq("Validate"));
68
- };
69
-
70
68
  /** Start the webhook server */
71
- public startServer = (port: number) => {
72
- if (this._running) {
69
+ startServer(port: number) {
70
+ if (this.#running) {
73
71
  throw new Error("Cannot start Pepr module: Pepr module was not instantiated with deferStart=true");
74
72
  }
75
73
 
@@ -82,22 +80,22 @@ export class Controller {
82
80
  // Get the API token if not in watch mode
83
81
  if (!isWatchMode) {
84
82
  // Get the API token from the environment variable or the mounted secret
85
- this._token = process.env.PEPR_API_TOKEN || fs.readFileSync("/app/api-token/value").toString().trim();
86
- Log.info(`Using API token: ${this._token}`);
83
+ this.#token = process.env.PEPR_API_TOKEN || fs.readFileSync("/app/api-token/value").toString().trim();
84
+ Log.info(`Using API token: ${this.#token}`);
87
85
 
88
- if (!this._token) {
86
+ if (!this.#token) {
89
87
  throw new Error("API token not found");
90
88
  }
91
89
  }
92
90
 
93
91
  // Create HTTPS server
94
- const server = https.createServer(options, this._app).listen(port);
92
+ const server = https.createServer(options, this.#app).listen(port);
95
93
 
96
94
  // Handle server listening event
97
95
  server.on("listening", () => {
98
96
  Log.info(`Server listening on port ${port}`);
99
97
  // Track that the server is running
100
- this._running = true;
98
+ this.#running = true;
101
99
  });
102
100
 
103
101
  // Handle EADDRINUSE errors
@@ -121,32 +119,27 @@ export class Controller {
121
119
  process.exit(0);
122
120
  });
123
121
  });
124
- };
122
+ }
125
123
 
126
- /**
127
- * Middleware for logging requests
128
- *
129
- * @param req the incoming request
130
- * @param res the outgoing response
131
- * @param next the next middleware function
132
- */
133
- private logger = (req: express.Request, res: express.Response, next: express.NextFunction) => {
134
- const startTime = Date.now();
124
+ #bindEndpoints = () => {
125
+ // Health check endpoint
126
+ this.#app.get("/healthz", Controller.#healthz);
135
127
 
136
- res.on("finish", () => {
137
- const elapsedTime = Date.now() - startTime;
138
- const message = {
139
- uid: req.body?.request?.uid,
140
- method: req.method,
141
- url: req.originalUrl,
142
- status: res.statusCode,
143
- duration: `${elapsedTime} ms`,
144
- };
128
+ // Metrics endpoint
129
+ this.#app.get("/metrics", this.#metrics);
145
130
 
146
- res.statusCode >= 300 ? Log.warn(message) : Log.info(message);
147
- });
131
+ if (isWatchMode) {
132
+ return;
133
+ }
148
134
 
149
- next();
135
+ // Require auth for webhook endpoints
136
+ this.#app.use(["/mutate/:token", "/validate/:token"], this.#validateToken);
137
+
138
+ // Mutate endpoint
139
+ this.#app.post("/mutate/:token", this.#admissionReq("Mutate"));
140
+
141
+ // Validate endpoint
142
+ this.#app.post("/validate/:token", this.#admissionReq("Validate"));
150
143
  };
151
144
 
152
145
  /**
@@ -157,14 +150,14 @@ export class Controller {
157
150
  * @param next The next middleware function
158
151
  * @returns
159
152
  */
160
- private validateToken = (req: express.Request, res: express.Response, next: NextFunction) => {
153
+ #validateToken = (req: express.Request, res: express.Response, next: NextFunction) => {
161
154
  // Validate the token
162
155
  const { token } = req.params;
163
- if (token !== this._token) {
156
+ if (token !== this.#token) {
164
157
  const err = `Unauthorized: invalid token '${token.replace(/[^\w]/g, "_")}'`;
165
158
  Log.warn(err);
166
159
  res.status(401).send(err);
167
- this.metricsCollector.alert();
160
+ this.#metricsCollector.alert();
168
161
  return;
169
162
  }
170
163
 
@@ -172,30 +165,15 @@ export class Controller {
172
165
  next();
173
166
  };
174
167
 
175
- /**
176
- * Health check endpoint handler
177
- *
178
- * @param req the incoming request
179
- * @param res the outgoing response
180
- */
181
- private healthz = (req: express.Request, res: express.Response) => {
182
- try {
183
- res.send("OK");
184
- } catch (err) {
185
- Log.error(err);
186
- res.status(500).send("Internal Server Error");
187
- }
188
- };
189
-
190
168
  /**
191
169
  * Metrics endpoint handler
192
170
  *
193
171
  * @param req the incoming request
194
172
  * @param res the outgoing response
195
173
  */
196
- private metrics = async (req: express.Request, res: express.Response) => {
174
+ #metrics = async (req: express.Request, res: express.Response) => {
197
175
  try {
198
- res.send(await this.metricsCollector.getMetrics());
176
+ res.send(await this.#metricsCollector.getMetrics());
199
177
  } catch (err) {
200
178
  Log.error(err);
201
179
  res.status(500).send("Internal Server Error");
@@ -208,18 +186,18 @@ export class Controller {
208
186
  * @param admissionKind the type of admission request
209
187
  * @returns the request handler
210
188
  */
211
- private admissionReq = (admissionKind: "Mutate" | "Validate") => {
189
+ #admissionReq = (admissionKind: "Mutate" | "Validate") => {
212
190
  // Create the admission request handler
213
191
  return async (req: express.Request, res: express.Response) => {
214
192
  // Start the metrics timer
215
- const startTime = this.metricsCollector.observeStart();
193
+ const startTime = this.#metricsCollector.observeStart();
216
194
 
217
195
  try {
218
196
  // Get the request from the body or create an empty request
219
197
  const request: Request = req.body?.request || ({} as Request);
220
198
 
221
199
  // Run the before hook if it exists
222
- this._beforeHook && this._beforeHook(request || {});
200
+ this.#beforeHook && this.#beforeHook(request || {});
223
201
 
224
202
  // Setup identifiers for logging
225
203
  const name = request?.name ? `/${request.name}` : "";
@@ -240,13 +218,13 @@ export class Controller {
240
218
 
241
219
  // Call mutate or validate based on the admission kind
242
220
  if (admissionKind === "Mutate") {
243
- response = await mutateProcessor(this._config, this._capabilities, request, reqMetadata);
221
+ response = await mutateProcessor(this.#config, this.#capabilities, request, reqMetadata);
244
222
  } else {
245
- response = await validateProcessor(this._capabilities, request, reqMetadata);
223
+ response = await validateProcessor(this.#capabilities, request, reqMetadata);
246
224
  }
247
225
 
248
226
  // Run the after hook if it exists
249
- this._afterHook && this._afterHook(response);
227
+ this.#afterHook && this.#afterHook(response);
250
228
 
251
229
  // Log the response
252
230
  Log.debug({ ...reqMetadata, response }, "Outgoing response");
@@ -257,12 +235,52 @@ export class Controller {
257
235
  kind: "AdmissionReview",
258
236
  response,
259
237
  });
260
- this.metricsCollector.observeEnd(startTime, admissionKind);
238
+ this.#metricsCollector.observeEnd(startTime, admissionKind);
261
239
  } catch (err) {
262
240
  Log.error(err);
263
241
  res.status(500).send("Internal Server Error");
264
- this.metricsCollector.error();
242
+ this.#metricsCollector.error();
265
243
  }
266
244
  };
267
245
  };
246
+
247
+ /**
248
+ * Middleware for logging requests
249
+ *
250
+ * @param req the incoming request
251
+ * @param res the outgoing response
252
+ * @param next the next middleware function
253
+ */
254
+ static #logger(req: express.Request, res: express.Response, next: express.NextFunction) {
255
+ const startTime = Date.now();
256
+
257
+ res.on("finish", () => {
258
+ const elapsedTime = Date.now() - startTime;
259
+ const message = {
260
+ uid: req.body?.request?.uid,
261
+ method: req.method,
262
+ url: req.originalUrl,
263
+ status: res.statusCode,
264
+ duration: `${elapsedTime} ms`,
265
+ };
266
+
267
+ res.statusCode >= 300 ? Log.warn(message) : Log.info(message);
268
+ });
269
+
270
+ next();
271
+ }
272
+ /**
273
+ * Health check endpoint handler
274
+ *
275
+ * @param req the incoming request
276
+ * @param res the outgoing response
277
+ */
278
+ static #healthz(req: express.Request, res: express.Response) {
279
+ try {
280
+ res.send("OK");
281
+ } catch (err) {
282
+ Log.error(err);
283
+ res.status(500).send("Internal Server Error");
284
+ }
285
+ }
268
286
  }
@@ -0,0 +1,20 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // SPDX-FileCopyrightText: 2023-Present The Pepr Authors
3
+
4
+ export const Errors = {
5
+ audit: "audit",
6
+ ignore: "ignore",
7
+ reject: "reject",
8
+ };
9
+
10
+ export const ErrorList = Object.values(Errors);
11
+
12
+ /**
13
+ * Validate the error or throw an error
14
+ * @param error
15
+ */
16
+ export function ValidateError(error = "") {
17
+ if (!ErrorList.includes(error)) {
18
+ throw new Error(`Invalid error: ${error}. Must be one of: ${ErrorList.join(", ")}`);
19
+ }
20
+ }
@@ -144,7 +144,11 @@ export interface MutateResponse {
144
144
  /** The type of Patch. Currently we only allow "JSONPatch". */
145
145
  patchType?: "JSONPatch";
146
146
 
147
- /** AuditAnnotations is an unstructured key value map set by remote admission controller (e.g. error=image-blacklisted). */
147
+ /**
148
+ * AuditAnnotations is an unstructured key value map set by remote admission controller (e.g. error=image-blacklisted).
149
+ *
150
+ * See https://kubernetes.io/docs/reference/labels-annotations-taints/audit-annotations/ for more information
151
+ */
148
152
  auditAnnotations?: {
149
153
  [key: string]: string;
150
154
  };
@@ -1,8 +1,10 @@
1
1
  // SPDX-License-Identifier: Apache-2.0
2
2
  // SPDX-FileCopyrightText: 2023-Present The Pepr Authors
3
3
 
4
- import promClient, { Counter, Summary, Registry } from "prom-client";
4
+ /* eslint-disable class-methods-use-this */
5
+
5
6
  import { performance } from "perf_hooks";
7
+ import promClient, { Counter, Registry, Summary } from "prom-client";
6
8
  import Log from "./logger";
7
9
 
8
10
  const loggingPrefix = "MetricsCollector";
@@ -24,12 +26,12 @@ interface MetricArgs {
24
26
  * MetricsCollector class handles metrics collection using prom-client and performance hooks.
25
27
  */
26
28
  export class MetricsCollector {
27
- private _registry: Registry;
28
- private _counters: Map<string, Counter<string>> = new Map();
29
- private _summaries: Map<string, Summary<string>> = new Map();
30
- private _prefix: string;
29
+ #registry: Registry;
30
+ #counters: Map<string, Counter<string>> = new Map();
31
+ #summaries: Map<string, Summary<string>> = new Map();
32
+ #prefix: string;
31
33
 
32
- private _metricNames: MetricNames = {
34
+ #metricNames: MetricNames = {
33
35
  errors: "errors",
34
36
  alerts: "alerts",
35
37
  mutate: "Mutate",
@@ -38,67 +40,78 @@ export class MetricsCollector {
38
40
 
39
41
  /**
40
42
  * Creates a MetricsCollector instance with prefixed metrics.
41
- * @param {string} [prefix='pepr'] - The prefix for the metric names.
43
+ * @param [prefix='pepr'] - The prefix for the metric names.
42
44
  */
43
45
  constructor(prefix = "pepr") {
44
- this._registry = new Registry();
45
- this._prefix = prefix;
46
- this.addCounter(this._metricNames.errors, "Mutation/Validate errors encountered");
47
- this.addCounter(this._metricNames.alerts, "Mutation/Validate bad api token received");
48
- this.addSummary(this._metricNames.mutate, "Mutation operation summary");
49
- this.addSummary(this._metricNames.validate, "Validation operation summary");
46
+ this.#registry = new Registry();
47
+ this.#prefix = prefix;
48
+ this.addCounter(this.#metricNames.errors, "Mutation/Validate errors encountered");
49
+ this.addCounter(this.#metricNames.alerts, "Mutation/Validate bad api token received");
50
+ this.addSummary(this.#metricNames.mutate, "Mutation operation summary");
51
+ this.addSummary(this.#metricNames.validate, "Validation operation summary");
50
52
  }
51
- private getMetricName(name: string) {
52
- return `${this._prefix}_${name}`;
53
+
54
+ #getMetricName(name: string) {
55
+ return `${this.#prefix}_${name}`;
53
56
  }
54
57
 
55
- private addMetric<T extends Counter<string> | Summary<string>>(
58
+ #addMetric<T extends Counter<string> | Summary<string>>(
56
59
  collection: Map<string, T>,
57
60
  MetricType: new (args: MetricArgs) => T,
58
61
  name: string,
59
62
  help: string,
60
63
  ) {
61
- if (collection.has(this.getMetricName(name))) {
64
+ if (collection.has(this.#getMetricName(name))) {
62
65
  Log.debug(`Metric for ${name} already exists`, loggingPrefix);
63
66
  return;
64
67
  }
68
+
69
+ // Bind public methods
70
+ this.incCounter = this.incCounter.bind(this);
71
+ this.error = this.error.bind(this);
72
+ this.alert = this.alert.bind(this);
73
+ this.observeStart = this.observeStart.bind(this);
74
+ this.observeEnd = this.observeEnd.bind(this);
75
+ this.getMetrics = this.getMetrics.bind(this);
76
+
65
77
  const metric = new MetricType({
66
- name: this.getMetricName(name),
78
+ name: this.#getMetricName(name),
67
79
  help,
68
- registers: [this._registry],
80
+ registers: [this.#registry],
69
81
  });
70
- collection.set(this.getMetricName(name), metric);
82
+
83
+ collection.set(this.#getMetricName(name), metric);
71
84
  }
72
85
 
73
86
  addCounter(name: string, help: string) {
74
- this.addMetric(this._counters, promClient.Counter, name, help);
87
+ this.#addMetric(this.#counters, promClient.Counter, name, help);
75
88
  }
76
89
 
77
90
  addSummary(name: string, help: string) {
78
- this.addMetric(this._summaries, promClient.Summary, name, help);
91
+ this.#addMetric(this.#summaries, promClient.Summary, name, help);
79
92
  }
80
93
 
81
94
  incCounter(name: string) {
82
- this._counters.get(this.getMetricName(name))?.inc();
95
+ this.#counters.get(this.#getMetricName(name))?.inc();
83
96
  }
84
97
 
85
98
  /**
86
99
  * Increments the error counter.
87
100
  */
88
101
  error() {
89
- this.incCounter(this._metricNames.errors);
102
+ this.incCounter(this.#metricNames.errors);
90
103
  }
91
104
 
92
105
  /**
93
106
  * Increments the alerts counter.
94
107
  */
95
108
  alert() {
96
- this.incCounter(this._metricNames.alerts);
109
+ this.incCounter(this.#metricNames.alerts);
97
110
  }
98
111
 
99
112
  /**
100
113
  * Returns the current timestamp from performance.now() method. Useful for start timing an operation.
101
- * @returns {number} The timestamp.
114
+ * @returns The timestamp.
102
115
  */
103
116
  observeStart() {
104
117
  return performance.now();
@@ -106,18 +119,18 @@ export class MetricsCollector {
106
119
 
107
120
  /**
108
121
  * Observes the duration since the provided start time and updates the summary.
109
- * @param {number} startTime - The start time.
110
- * @param {string} name - The metrics summary to increment.
122
+ * @param startTime - The start time.
123
+ * @param name - The metrics summary to increment.
111
124
  */
112
- observeEnd(startTime: number, name: string = this._metricNames.mutate) {
113
- this._summaries.get(this.getMetricName(name))?.observe(performance.now() - startTime);
125
+ observeEnd(startTime: number, name: string = this.#metricNames.mutate) {
126
+ this.#summaries.get(this.#getMetricName(name))?.observe(performance.now() - startTime);
114
127
  }
115
128
 
116
129
  /**
117
130
  * Fetches the current metrics from the registry.
118
- * @returns {Promise<string>} The metrics.
131
+ * @returns The metrics.
119
132
  */
120
133
  async getMetrics() {
121
- return this._registry.metrics();
134
+ return this.#registry.metrics();
122
135
  }
123
136
  }