kubernetes-fluent-client 1.10.0 → 2.0.1
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 +5 -3
- package/dist/fluent/index.d.ts +1 -1
- package/dist/fluent/index.d.ts.map +1 -1
- package/dist/fluent/index.js +2 -2
- package/dist/fluent/types.d.ts +11 -9
- package/dist/fluent/types.d.ts.map +1 -1
- package/dist/fluent/types.js +2 -0
- package/dist/fluent/watch.d.ts +78 -35
- package/dist/fluent/watch.d.ts.map +1 -1
- package/dist/fluent/watch.js +289 -107
- package/dist/fluent/watch.spec.d.ts +2 -0
- package/dist/fluent/watch.spec.d.ts.map +1 -0
- package/dist/fluent/watch.spec.js +235 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/package.json +5 -5
- package/src/fluent/index.ts +4 -4
- package/src/fluent/types.ts +13 -11
- package/src/fluent/watch.spec.ts +281 -0
- package/src/fluent/watch.ts +319 -150
- package/src/index.ts +3 -0
package/src/fluent/watch.ts
CHANGED
|
@@ -2,172 +2,237 @@
|
|
|
2
2
|
// SPDX-FileCopyrightText: 2023-Present The Kubernetes Fluent Client Authors
|
|
3
3
|
|
|
4
4
|
import byline from "byline";
|
|
5
|
+
import { createHash } from "crypto";
|
|
6
|
+
import { EventEmitter } from "events";
|
|
5
7
|
import fetch from "node-fetch";
|
|
6
8
|
|
|
7
|
-
import { GenericClass
|
|
9
|
+
import { GenericClass } from "../types";
|
|
8
10
|
import { Filters, WatchAction, WatchPhase } from "./types";
|
|
9
11
|
import { k8sCfg, pathBuilder } from "./utils";
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
13
|
+
export enum WatchEvent {
|
|
14
|
+
/** Watch is connected successfully */
|
|
15
|
+
CONNECT = "connect",
|
|
16
|
+
/** Network error occurs */
|
|
17
|
+
NETWORK_ERROR = "network_error",
|
|
18
|
+
/** Error decoding data or running the callback */
|
|
19
|
+
DATA_ERROR = "data_error",
|
|
20
|
+
/** Reconnect is called */
|
|
21
|
+
RECONNECT = "reconnect",
|
|
22
|
+
/** Retry limit is exceeded */
|
|
23
|
+
GIVE_UP = "give_up",
|
|
24
|
+
/** Abort is called */
|
|
25
|
+
ABORT = "abort",
|
|
26
|
+
/** Resync is called */
|
|
27
|
+
RESYNC = "resync",
|
|
28
|
+
/** Data is received and decoded */
|
|
29
|
+
DATA = "data",
|
|
30
|
+
/** Bookmark is received */
|
|
31
|
+
BOOKMARK = "bookmark",
|
|
32
|
+
/** ResourceVersion is updated */
|
|
33
|
+
RESOURCE_VERSION = "resource_version",
|
|
34
|
+
/** 410 (old resource version) occurs */
|
|
35
|
+
OLD_RESOURCE_VERSION = "old_resource_version",
|
|
36
|
+
/** A reconnect is already pending */
|
|
37
|
+
RECONNECT_PENDING = "reconnect_pending",
|
|
38
|
+
}
|
|
28
39
|
|
|
29
|
-
/**
|
|
30
|
-
* Configuration for the watch function.
|
|
31
|
-
*/
|
|
40
|
+
/** Configuration for the watch function. */
|
|
32
41
|
export type WatchCfg = {
|
|
33
|
-
/**
|
|
34
|
-
|
|
35
|
-
|
|
42
|
+
/** The resource version to start the watch at, this will be updated on each event. */
|
|
43
|
+
resourceVersion?: string;
|
|
44
|
+
/** The maximum number of times to retry the watch, the retry count is reset on success. Unlimited retries if not specified. */
|
|
36
45
|
retryMax?: number;
|
|
37
|
-
/**
|
|
38
|
-
* The delay between retries in seconds.
|
|
39
|
-
*/
|
|
46
|
+
/** The delay between retries in seconds. Defaults to 10 seconds. */
|
|
40
47
|
retryDelaySec?: number;
|
|
41
|
-
/**
|
|
42
|
-
|
|
43
|
-
*/
|
|
44
|
-
logFn?: LogFn;
|
|
45
|
-
/**
|
|
46
|
-
* A function to call when the watch fails after the maximum number of retries.
|
|
47
|
-
*/
|
|
48
|
-
retryFail?: (e: Error) => void;
|
|
48
|
+
/** Amount of seconds to wait before a forced-resyncing of the watch list. Defaults to 300 (5 minutes). */
|
|
49
|
+
resyncIntervalSec?: number;
|
|
49
50
|
};
|
|
50
51
|
|
|
51
|
-
/**
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
* @returns a WatchController to allow the watch to be aborted externally
|
|
59
|
-
*/
|
|
60
|
-
export async function ExecWatch<T extends GenericClass>(
|
|
61
|
-
model: T,
|
|
62
|
-
filters: Filters,
|
|
63
|
-
callback: WatchAction<T>,
|
|
64
|
-
watchCfg: WatchCfg = {},
|
|
65
|
-
) {
|
|
66
|
-
watchCfg.logFn?.({ model, filters, watchCfg }, "ExecWatch");
|
|
67
|
-
|
|
68
|
-
// Build the path and query params for the resource, excluding the name
|
|
69
|
-
const { opts, serverUrl } = await k8sCfg("GET");
|
|
70
|
-
const url = pathBuilder(serverUrl, model, filters, true);
|
|
71
|
-
|
|
72
|
-
// Enable the watch query param
|
|
73
|
-
url.searchParams.set("watch", "true");
|
|
74
|
-
|
|
75
|
-
// Allow bookmarks to be used for the watch
|
|
76
|
-
url.searchParams.set("allowWatchBookmarks", "true");
|
|
77
|
-
|
|
78
|
-
// If a name is specified, add it to the query params
|
|
79
|
-
if (filters.name) {
|
|
80
|
-
url.searchParams.set("fieldSelector", `metadata.name=${filters.name}`);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// Set the initial timeout to 15 seconds
|
|
84
|
-
opts.timeout = 15 * 1000;
|
|
52
|
+
/** A wrapper around the Kubernetes watch API. */
|
|
53
|
+
export class Watcher<T extends GenericClass> {
|
|
54
|
+
// User-provided properties
|
|
55
|
+
#model: T;
|
|
56
|
+
#filters: Filters;
|
|
57
|
+
#callback: WatchAction<T>;
|
|
58
|
+
#watchCfg: WatchCfg;
|
|
85
59
|
|
|
86
|
-
//
|
|
87
|
-
|
|
60
|
+
// Create a wrapped AbortController to allow the watch to be aborted externally
|
|
61
|
+
#abortController: AbortController;
|
|
88
62
|
|
|
89
63
|
// Track the number of retries
|
|
90
|
-
|
|
64
|
+
#retryCount = 0;
|
|
91
65
|
|
|
92
|
-
//
|
|
93
|
-
|
|
66
|
+
// Create a stream to read the response body
|
|
67
|
+
#stream?: byline.LineStream;
|
|
94
68
|
|
|
95
|
-
//
|
|
96
|
-
|
|
69
|
+
// Create an EventEmitter to emit events
|
|
70
|
+
#events = new EventEmitter();
|
|
97
71
|
|
|
98
|
-
// Create a
|
|
99
|
-
|
|
72
|
+
// Create a timer to resync the watch
|
|
73
|
+
#resyncTimer?: NodeJS.Timeout;
|
|
100
74
|
|
|
101
|
-
//
|
|
102
|
-
|
|
75
|
+
// Track if a reconnect is pending
|
|
76
|
+
#pendingReconnect = false;
|
|
103
77
|
|
|
104
78
|
/**
|
|
105
|
-
*
|
|
79
|
+
* Setup a Kubernetes watcher for the specified model and filters. The callback function will be called for each event received.
|
|
80
|
+
* The watch can be aborted by calling {@link Watcher.close} or by calling abort() on the AbortController returned by {@link Watcher.start}.
|
|
81
|
+
*
|
|
82
|
+
*
|
|
83
|
+
* Kubernetes API docs: {@link https://kubernetes.io/docs/reference/using-api/api-concepts/#efficient-detection-of-changes}
|
|
84
|
+
*
|
|
85
|
+
* @param model - the model to use for the API
|
|
86
|
+
* @param filters - (optional) filter overrides, can also be chained
|
|
87
|
+
* @param callback - the callback function to call when an event is received
|
|
88
|
+
* @param watchCfg - (optional) watch configuration
|
|
106
89
|
*/
|
|
107
|
-
|
|
90
|
+
constructor(model: T, filters: Filters, callback: WatchAction<T>, watchCfg: WatchCfg = {}) {
|
|
91
|
+
// Set the retry delay to 10 seconds if not specified
|
|
92
|
+
watchCfg.retryDelaySec ??= 10;
|
|
93
|
+
|
|
94
|
+
// Set the resync interval to 5 minutes if not specified
|
|
95
|
+
watchCfg.resyncIntervalSec ??= 300;
|
|
96
|
+
|
|
97
|
+
// Bind class properties
|
|
98
|
+
this.#model = model;
|
|
99
|
+
this.#filters = filters;
|
|
100
|
+
this.#callback = callback;
|
|
101
|
+
this.#watchCfg = watchCfg;
|
|
102
|
+
|
|
108
103
|
// Create a new AbortController
|
|
109
|
-
abortController = new AbortController();
|
|
104
|
+
this.#abortController = new AbortController();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Start the watch.
|
|
109
|
+
*
|
|
110
|
+
* @returns The AbortController for the watch.
|
|
111
|
+
*/
|
|
112
|
+
public async start(): Promise<AbortController> {
|
|
113
|
+
await this.#runner();
|
|
114
|
+
return this.#abortController;
|
|
115
|
+
}
|
|
110
116
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
117
|
+
/** Close the watch. Also available on the AbortController returned by {@link Watcher.start}. */
|
|
118
|
+
public close() {
|
|
119
|
+
clearTimeout(this.#resyncTimer);
|
|
120
|
+
this.#cleanup();
|
|
121
|
+
this.#abortController.abort();
|
|
122
|
+
}
|
|
114
123
|
|
|
115
|
-
|
|
116
|
-
|
|
124
|
+
/**
|
|
125
|
+
* Get a unique ID for the watch based on the model and filters.
|
|
126
|
+
* This is useful for caching the watch data or resource versions.
|
|
127
|
+
*
|
|
128
|
+
* @returns the watch CacheID
|
|
129
|
+
*/
|
|
130
|
+
public getCacheID() {
|
|
131
|
+
// Build the URL, we don't care about the server URL or resourceVersion
|
|
132
|
+
const url = pathBuilder("https://ignore", this.#model, this.#filters, false);
|
|
133
|
+
|
|
134
|
+
// Hash and truncate the ID to 10 characters, cache the result
|
|
135
|
+
return createHash("sha224")
|
|
136
|
+
.update(url.pathname + url.search)
|
|
137
|
+
.digest("hex")
|
|
138
|
+
.substring(0, 10);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Get the current resource version.
|
|
143
|
+
*
|
|
144
|
+
* @returns the current resource version
|
|
145
|
+
*/
|
|
146
|
+
public get resourceVersion() {
|
|
147
|
+
return this.#watchCfg.resourceVersion;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Set the current resource version.
|
|
152
|
+
*
|
|
153
|
+
* @param resourceVersion - the new resource version
|
|
154
|
+
*/
|
|
155
|
+
public set resourceVersion(resourceVersion: string | undefined) {
|
|
156
|
+
this.#watchCfg.resourceVersion = resourceVersion;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Subscribe to watch events. This is an EventEmitter that emits the following events:
|
|
161
|
+
*
|
|
162
|
+
* Use {@link WatchEvent} for the event names.
|
|
163
|
+
*
|
|
164
|
+
* @returns an EventEmitter
|
|
165
|
+
*/
|
|
166
|
+
public get events(): EventEmitter {
|
|
167
|
+
return this.#events;
|
|
117
168
|
}
|
|
118
169
|
|
|
119
170
|
/**
|
|
120
|
-
*
|
|
171
|
+
* Build the URL and request options for the watch.
|
|
172
|
+
*
|
|
173
|
+
* @returns the URL and request options
|
|
121
174
|
*/
|
|
122
|
-
async
|
|
123
|
-
|
|
175
|
+
#buildURL = async () => {
|
|
176
|
+
// Build the path and query params for the resource, excluding the name
|
|
177
|
+
const { opts, serverUrl } = await k8sCfg("GET");
|
|
178
|
+
const url = pathBuilder(serverUrl, this.#model, this.#filters, true);
|
|
124
179
|
|
|
125
|
-
|
|
180
|
+
// Enable the watch query param
|
|
181
|
+
url.searchParams.set("watch", "true");
|
|
126
182
|
|
|
127
|
-
//
|
|
128
|
-
|
|
183
|
+
// If a name is specified, add it to the query params
|
|
184
|
+
if (this.#filters.name) {
|
|
185
|
+
url.searchParams.set("fieldSelector", `metadata.name=${this.#filters.name}`);
|
|
186
|
+
}
|
|
129
187
|
|
|
130
|
-
|
|
131
|
-
|
|
188
|
+
// If a resource version is specified, add it to the query params
|
|
189
|
+
if (this.#watchCfg.resourceVersion) {
|
|
190
|
+
url.searchParams.set("resourceVersion", this.#watchCfg.resourceVersion);
|
|
191
|
+
}
|
|
132
192
|
|
|
133
|
-
|
|
134
|
-
|
|
193
|
+
// Enable watch bookmarks
|
|
194
|
+
url.searchParams.set("allowWatchBookmarks", "true");
|
|
135
195
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
watchCfg.logFn?.(err, "stream error");
|
|
139
|
-
void reload(err);
|
|
140
|
-
} else {
|
|
141
|
-
watchCfg.logFn?.("watch aborted via WatchController.abort()");
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
};
|
|
196
|
+
// Add the abort signal to the request options
|
|
197
|
+
opts.signal = this.#abortController.signal;
|
|
145
198
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
if (!doneCalled) {
|
|
149
|
-
doneCalled = true;
|
|
150
|
-
stream.removeAllListeners();
|
|
151
|
-
}
|
|
152
|
-
};
|
|
199
|
+
return { opts, url };
|
|
200
|
+
};
|
|
153
201
|
|
|
202
|
+
/** Run the watch. */
|
|
203
|
+
#runner = async () => {
|
|
154
204
|
try {
|
|
205
|
+
// Build the URL and request options
|
|
206
|
+
const { opts, url } = await this.#buildURL();
|
|
207
|
+
|
|
208
|
+
// Create a stream to read the response body
|
|
209
|
+
this.#stream = byline.createStream();
|
|
210
|
+
|
|
211
|
+
// Bind the stream events
|
|
212
|
+
this.#stream.on("error", this.#errHandler);
|
|
213
|
+
this.#stream.on("close", this.#cleanup);
|
|
214
|
+
this.#stream.on("finish", this.#cleanup);
|
|
215
|
+
|
|
155
216
|
// Make the actual request
|
|
156
217
|
const response = await fetch(url, { ...opts });
|
|
157
218
|
|
|
219
|
+
// Reset the pending reconnect flag
|
|
220
|
+
this.#pendingReconnect = false;
|
|
221
|
+
|
|
222
|
+
// Reset the resync timer
|
|
223
|
+
void this.#scheduleResync();
|
|
224
|
+
|
|
158
225
|
// If the request is successful, start listening for events
|
|
159
226
|
if (response.ok) {
|
|
227
|
+
this.#events.emit(WatchEvent.CONNECT);
|
|
228
|
+
|
|
160
229
|
const { body } = response;
|
|
161
230
|
|
|
162
231
|
// Reset the retry count
|
|
163
|
-
retryCount = 0;
|
|
164
|
-
|
|
165
|
-
stream.on("error", onError);
|
|
166
|
-
stream.on("close", cleanup);
|
|
167
|
-
stream.on("finish", cleanup);
|
|
232
|
+
this.#retryCount = 0;
|
|
168
233
|
|
|
169
234
|
// Listen for events and call the callback function
|
|
170
|
-
stream.on("data", line => {
|
|
235
|
+
this.#stream.on("data", async line => {
|
|
171
236
|
try {
|
|
172
237
|
// Parse the event payload
|
|
173
238
|
const { object: payload, type: phase } = JSON.parse(line) as {
|
|
@@ -175,53 +240,157 @@ export async function ExecWatch<T extends GenericClass>(
|
|
|
175
240
|
object: InstanceType<T>;
|
|
176
241
|
};
|
|
177
242
|
|
|
178
|
-
|
|
179
|
-
|
|
243
|
+
void this.#scheduleResync();
|
|
244
|
+
|
|
245
|
+
// If the watch is too old, remove the resourceVersion and reload the watch
|
|
246
|
+
if (phase === WatchPhase.Error && payload.code === 410) {
|
|
247
|
+
throw {
|
|
248
|
+
name: "TooOld",
|
|
249
|
+
message: this.#watchCfg.resourceVersion!,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// If the event is a bookmark, emit the event and skip the callback
|
|
254
|
+
if (phase === WatchPhase.Bookmark) {
|
|
255
|
+
this.#events.emit(WatchEvent.BOOKMARK, payload);
|
|
256
|
+
} else {
|
|
257
|
+
this.#events.emit(WatchEvent.DATA, payload, phase);
|
|
258
|
+
|
|
259
|
+
// Call the callback function with the parsed payload
|
|
260
|
+
await this.#callback(payload, phase as WatchPhase);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Update the resource version if the callback was successful
|
|
264
|
+
this.#setResourceVersion(payload.metadata.resourceVersion);
|
|
180
265
|
} catch (err) {
|
|
181
|
-
|
|
266
|
+
if (err.name === "TooOld") {
|
|
267
|
+
// Prevent any body events from firing
|
|
268
|
+
body.removeAllListeners();
|
|
269
|
+
|
|
270
|
+
// Reload the watch
|
|
271
|
+
void this.#errHandler(err);
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
this.#events.emit(WatchEvent.DATA_ERROR, err);
|
|
182
276
|
}
|
|
183
277
|
});
|
|
184
278
|
|
|
185
|
-
body
|
|
186
|
-
body.on("
|
|
187
|
-
body.on("
|
|
279
|
+
// Bind the body events
|
|
280
|
+
body.on("error", this.#errHandler);
|
|
281
|
+
body.on("close", this.#cleanup);
|
|
282
|
+
body.on("finish", this.#cleanup);
|
|
188
283
|
|
|
189
284
|
// Pipe the response body to the stream
|
|
190
|
-
body.pipe(stream);
|
|
285
|
+
body.pipe(this.#stream);
|
|
191
286
|
} else {
|
|
192
|
-
throw new Error(`watch failed: ${response.status} ${response.statusText}`);
|
|
287
|
+
throw new Error(`watch connect failed: ${response.status} ${response.statusText}`);
|
|
193
288
|
}
|
|
194
289
|
} catch (e) {
|
|
195
|
-
|
|
290
|
+
void this.#errHandler(e);
|
|
196
291
|
}
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Resync the watch.
|
|
296
|
+
*
|
|
297
|
+
* @returns the error handler
|
|
298
|
+
*/
|
|
299
|
+
#resync = () =>
|
|
300
|
+
this.#errHandler({
|
|
301
|
+
name: "Resync",
|
|
302
|
+
message: "Resync triggered by resyncIntervalSec",
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
/** Clear the resync timer and schedule a new one. */
|
|
306
|
+
#scheduleResync = async () => {
|
|
307
|
+
clearTimeout(this.#resyncTimer);
|
|
308
|
+
this.#resyncTimer = setTimeout(this.#resync, this.#watchCfg.resyncIntervalSec! * 1000);
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Update the resource version.
|
|
313
|
+
*
|
|
314
|
+
* @param resourceVersion - the new resource version
|
|
315
|
+
*/
|
|
316
|
+
#setResourceVersion = (resourceVersion?: string) => {
|
|
317
|
+
this.#watchCfg.resourceVersion = resourceVersion;
|
|
318
|
+
this.#events.emit(WatchEvent.RESOURCE_VERSION, resourceVersion);
|
|
319
|
+
};
|
|
197
320
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
321
|
+
/**
|
|
322
|
+
* Reload the watch after an error.
|
|
323
|
+
*
|
|
324
|
+
* @param err - the error that occurred
|
|
325
|
+
*/
|
|
326
|
+
#reconnect = async (err: Error) => {
|
|
327
|
+
// If there are more attempts, retry the watch (undefined is unlimited retries)
|
|
328
|
+
if (this.#watchCfg.retryMax === undefined || this.#watchCfg.retryMax > this.#retryCount) {
|
|
329
|
+
// Sleep for the specified delay, but check every 500ms if the watch has been aborted
|
|
330
|
+
let delay = this.#watchCfg.retryDelaySec! * 1000;
|
|
331
|
+
while (delay > 0) {
|
|
332
|
+
if (this.#abortController.signal.aborted) {
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
delay -= 500;
|
|
336
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
337
|
+
}
|
|
207
338
|
|
|
208
|
-
|
|
339
|
+
this.#retryCount++;
|
|
340
|
+
this.#events.emit(WatchEvent.RECONNECT, err, this.#retryCount);
|
|
209
341
|
|
|
210
|
-
|
|
211
|
-
|
|
342
|
+
if (this.#pendingReconnect) {
|
|
343
|
+
this.#events.emit(WatchEvent.RECONNECT_PENDING);
|
|
344
|
+
} else {
|
|
345
|
+
this.#pendingReconnect = true;
|
|
346
|
+
this.#cleanup();
|
|
212
347
|
|
|
213
348
|
// Retry the watch after the delay
|
|
214
|
-
await runner();
|
|
215
|
-
} else {
|
|
216
|
-
// Otherwise, call the finally function if it exists
|
|
217
|
-
if (watchCfg.retryFail) {
|
|
218
|
-
watchCfg.retryFail(e);
|
|
219
|
-
}
|
|
349
|
+
await this.#runner();
|
|
220
350
|
}
|
|
351
|
+
} else {
|
|
352
|
+
// Otherwise, call the finally function if it exists
|
|
353
|
+
this.#events.emit(WatchEvent.GIVE_UP, err);
|
|
354
|
+
this.close();
|
|
221
355
|
}
|
|
222
|
-
}
|
|
356
|
+
};
|
|
223
357
|
|
|
224
|
-
|
|
358
|
+
/**
|
|
359
|
+
* Handle errors from the stream.
|
|
360
|
+
*
|
|
361
|
+
* @param err - the error that occurred
|
|
362
|
+
*/
|
|
363
|
+
#errHandler = async (err: Error) => {
|
|
364
|
+
switch (err.name) {
|
|
365
|
+
case "AbortError":
|
|
366
|
+
clearTimeout(this.#resyncTimer);
|
|
367
|
+
this.#cleanup();
|
|
368
|
+
this.#events.emit(WatchEvent.ABORT, err);
|
|
369
|
+
return;
|
|
370
|
+
|
|
371
|
+
case "TooOld":
|
|
372
|
+
// Purge the resource version if it is too old
|
|
373
|
+
this.#setResourceVersion(undefined);
|
|
374
|
+
this.#events.emit(WatchEvent.OLD_RESOURCE_VERSION, err.message);
|
|
375
|
+
break;
|
|
376
|
+
|
|
377
|
+
case "Resync":
|
|
378
|
+
this.#events.emit(WatchEvent.RESYNC, err);
|
|
379
|
+
break;
|
|
380
|
+
|
|
381
|
+
default:
|
|
382
|
+
this.#events.emit(WatchEvent.NETWORK_ERROR, err);
|
|
383
|
+
break;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
await this.#reconnect(err);
|
|
387
|
+
};
|
|
225
388
|
|
|
226
|
-
|
|
389
|
+
/** Cleanup the stream and listeners. */
|
|
390
|
+
#cleanup = () => {
|
|
391
|
+
if (this.#stream) {
|
|
392
|
+
this.#stream.removeAllListeners();
|
|
393
|
+
this.#stream.destroy();
|
|
394
|
+
}
|
|
395
|
+
};
|
|
227
396
|
}
|
package/src/index.ts
CHANGED
|
@@ -15,6 +15,9 @@ export { fetch } from "./fetch";
|
|
|
15
15
|
// Export the HTTP status codes
|
|
16
16
|
export { StatusCodes as fetchStatus } from "http-status-codes";
|
|
17
17
|
|
|
18
|
+
// Export the Watch Config and Event types
|
|
19
|
+
export { WatchCfg, WatchEvent } from "./fluent/watch";
|
|
20
|
+
|
|
18
21
|
// Export the fluent API entrypoint
|
|
19
22
|
export { K8s } from "./fluent";
|
|
20
23
|
|