kubernetes-fluent-client 1.10.0 → 2.0.0
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/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 +70 -39
- package/dist/fluent/watch.d.ts.map +1 -1
- package/dist/fluent/watch.js +282 -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 +221 -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 +3 -3
- package/src/fluent/index.ts +4 -4
- package/src/fluent/types.ts +13 -11
- package/src/fluent/watch.spec.ts +264 -0
- package/src/fluent/watch.ts +313 -150
- package/src/index.ts +3 -0
package/src/fluent/watch.ts
CHANGED
|
@@ -2,172 +2,231 @@
|
|
|
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
|
+
// Unique ID for the watch
|
|
76
|
+
#id?: string;
|
|
77
|
+
#hashedID?: string;
|
|
78
|
+
|
|
79
|
+
// Track if a reconnect is pending
|
|
80
|
+
#pendingReconnect = false;
|
|
103
81
|
|
|
104
82
|
/**
|
|
105
|
-
*
|
|
83
|
+
* Setup a Kubernetes watcher for the specified model and filters. The callback function will be called for each event received.
|
|
84
|
+
* The watch can be aborted by calling {@link Watcher.close} or by calling abort() on the AbortController returned by {@link Watcher.start}.
|
|
85
|
+
*
|
|
86
|
+
*
|
|
87
|
+
* Kubernetes API docs: {@link https://kubernetes.io/docs/reference/using-api/api-concepts/#efficient-detection-of-changes}
|
|
88
|
+
*
|
|
89
|
+
* @param model - the model to use for the API
|
|
90
|
+
* @param filters - (optional) filter overrides, can also be chained
|
|
91
|
+
* @param callback - the callback function to call when an event is received
|
|
92
|
+
* @param watchCfg - (optional) watch configuration
|
|
106
93
|
*/
|
|
107
|
-
|
|
94
|
+
constructor(model: T, filters: Filters, callback: WatchAction<T>, watchCfg: WatchCfg = {}) {
|
|
95
|
+
// Set the retry delay to 10 seconds if not specified
|
|
96
|
+
watchCfg.retryDelaySec ??= 10;
|
|
97
|
+
|
|
98
|
+
// Set the resync interval to 5 minutes if not specified
|
|
99
|
+
watchCfg.resyncIntervalSec ??= 300;
|
|
100
|
+
|
|
101
|
+
// Bind class properties
|
|
102
|
+
this.#model = model;
|
|
103
|
+
this.#filters = filters;
|
|
104
|
+
this.#callback = callback;
|
|
105
|
+
this.#watchCfg = watchCfg;
|
|
106
|
+
|
|
108
107
|
// Create a new AbortController
|
|
109
|
-
abortController = new AbortController();
|
|
108
|
+
this.#abortController = new AbortController();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Start the watch.
|
|
113
|
+
*
|
|
114
|
+
* @returns The AbortController for the watch.
|
|
115
|
+
*/
|
|
116
|
+
public async start(): Promise<AbortController> {
|
|
117
|
+
await this.#runner();
|
|
118
|
+
return this.#abortController;
|
|
119
|
+
}
|
|
110
120
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
121
|
+
/** Close the watch. Also available on the AbortController returned by {@link Watcher.start}. */
|
|
122
|
+
public close() {
|
|
123
|
+
clearTimeout(this.#resyncTimer);
|
|
124
|
+
this.#cleanup();
|
|
125
|
+
this.#abortController.abort();
|
|
126
|
+
}
|
|
114
127
|
|
|
115
|
-
|
|
116
|
-
|
|
128
|
+
/**
|
|
129
|
+
* Get a unique ID for the watch based on the model and filters.
|
|
130
|
+
* This is useful for caching the watch data or resource versions.
|
|
131
|
+
*
|
|
132
|
+
* @returns the watch ID
|
|
133
|
+
*/
|
|
134
|
+
public get id() {
|
|
135
|
+
// The ID must exist at this point
|
|
136
|
+
if (!this.#id) {
|
|
137
|
+
throw new Error("watch not started");
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Hash and truncate the ID to 10 characters, cache the result
|
|
141
|
+
if (!this.#hashedID) {
|
|
142
|
+
this.#hashedID = createHash("sha224").update(this.#id).digest("hex").substring(0, 10);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return this.#hashedID;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Subscribe to watch events. This is an EventEmitter that emits the following events:
|
|
150
|
+
*
|
|
151
|
+
* Use {@link WatchEvent} for the event names.
|
|
152
|
+
*
|
|
153
|
+
* @returns an EventEmitter
|
|
154
|
+
*/
|
|
155
|
+
public get events(): EventEmitter {
|
|
156
|
+
return this.#events;
|
|
117
157
|
}
|
|
118
158
|
|
|
119
159
|
/**
|
|
120
|
-
*
|
|
160
|
+
* Build the URL and request options for the watch.
|
|
161
|
+
*
|
|
162
|
+
* @returns the URL and request options
|
|
121
163
|
*/
|
|
122
|
-
async
|
|
123
|
-
|
|
164
|
+
#buildURL = async () => {
|
|
165
|
+
// Build the path and query params for the resource, excluding the name
|
|
166
|
+
const { opts, serverUrl } = await k8sCfg("GET");
|
|
167
|
+
const url = pathBuilder(serverUrl, this.#model, this.#filters, true);
|
|
168
|
+
|
|
169
|
+
// Set the watch ID if it does not exist (this does not change on reconnect)
|
|
170
|
+
if (!this.#id) {
|
|
171
|
+
this.#id = url.pathname + url.search;
|
|
172
|
+
}
|
|
124
173
|
|
|
125
|
-
|
|
174
|
+
// Enable the watch query param
|
|
175
|
+
url.searchParams.set("watch", "true");
|
|
126
176
|
|
|
127
|
-
//
|
|
128
|
-
|
|
177
|
+
// If a name is specified, add it to the query params
|
|
178
|
+
if (this.#filters.name) {
|
|
179
|
+
url.searchParams.set("fieldSelector", `metadata.name=${this.#filters.name}`);
|
|
180
|
+
}
|
|
129
181
|
|
|
130
|
-
|
|
131
|
-
|
|
182
|
+
// If a resource version is specified, add it to the query params
|
|
183
|
+
if (this.#watchCfg.resourceVersion) {
|
|
184
|
+
url.searchParams.set("resourceVersion", this.#watchCfg.resourceVersion);
|
|
185
|
+
}
|
|
132
186
|
|
|
133
|
-
|
|
134
|
-
|
|
187
|
+
// Enable watch bookmarks
|
|
188
|
+
url.searchParams.set("allowWatchBookmarks", "true");
|
|
135
189
|
|
|
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
|
-
};
|
|
190
|
+
// Add the abort signal to the request options
|
|
191
|
+
opts.signal = this.#abortController.signal;
|
|
145
192
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
if (!doneCalled) {
|
|
149
|
-
doneCalled = true;
|
|
150
|
-
stream.removeAllListeners();
|
|
151
|
-
}
|
|
152
|
-
};
|
|
193
|
+
return { opts, url };
|
|
194
|
+
};
|
|
153
195
|
|
|
196
|
+
/** Run the watch. */
|
|
197
|
+
#runner = async () => {
|
|
154
198
|
try {
|
|
199
|
+
// Build the URL and request options
|
|
200
|
+
const { opts, url } = await this.#buildURL();
|
|
201
|
+
|
|
202
|
+
// Create a stream to read the response body
|
|
203
|
+
this.#stream = byline.createStream();
|
|
204
|
+
|
|
205
|
+
// Bind the stream events
|
|
206
|
+
this.#stream.on("error", this.#errHandler);
|
|
207
|
+
this.#stream.on("close", this.#cleanup);
|
|
208
|
+
this.#stream.on("finish", this.#cleanup);
|
|
209
|
+
|
|
155
210
|
// Make the actual request
|
|
156
211
|
const response = await fetch(url, { ...opts });
|
|
157
212
|
|
|
213
|
+
// Reset the pending reconnect flag
|
|
214
|
+
this.#pendingReconnect = false;
|
|
215
|
+
|
|
216
|
+
// Reset the resync timer
|
|
217
|
+
void this.#scheduleResync();
|
|
218
|
+
|
|
158
219
|
// If the request is successful, start listening for events
|
|
159
220
|
if (response.ok) {
|
|
221
|
+
this.#events.emit(WatchEvent.CONNECT);
|
|
222
|
+
|
|
160
223
|
const { body } = response;
|
|
161
224
|
|
|
162
225
|
// Reset the retry count
|
|
163
|
-
retryCount = 0;
|
|
164
|
-
|
|
165
|
-
stream.on("error", onError);
|
|
166
|
-
stream.on("close", cleanup);
|
|
167
|
-
stream.on("finish", cleanup);
|
|
226
|
+
this.#retryCount = 0;
|
|
168
227
|
|
|
169
228
|
// Listen for events and call the callback function
|
|
170
|
-
stream.on("data", line => {
|
|
229
|
+
this.#stream.on("data", async line => {
|
|
171
230
|
try {
|
|
172
231
|
// Parse the event payload
|
|
173
232
|
const { object: payload, type: phase } = JSON.parse(line) as {
|
|
@@ -175,53 +234,157 @@ export async function ExecWatch<T extends GenericClass>(
|
|
|
175
234
|
object: InstanceType<T>;
|
|
176
235
|
};
|
|
177
236
|
|
|
178
|
-
|
|
179
|
-
|
|
237
|
+
void this.#scheduleResync();
|
|
238
|
+
|
|
239
|
+
// If the watch is too old, remove the resourceVersion and reload the watch
|
|
240
|
+
if (phase === WatchPhase.Error && payload.code === 410) {
|
|
241
|
+
throw {
|
|
242
|
+
name: "TooOld",
|
|
243
|
+
message: this.#watchCfg.resourceVersion!,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// If the event is a bookmark, emit the event and skip the callback
|
|
248
|
+
if (phase === WatchPhase.Bookmark) {
|
|
249
|
+
this.#events.emit(WatchEvent.BOOKMARK, payload);
|
|
250
|
+
} else {
|
|
251
|
+
this.#events.emit(WatchEvent.DATA, payload, phase);
|
|
252
|
+
|
|
253
|
+
// Call the callback function with the parsed payload
|
|
254
|
+
await this.#callback(payload, phase as WatchPhase);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Update the resource version if the callback was successful
|
|
258
|
+
this.#setResourceVersion(payload.metadata.resourceVersion);
|
|
180
259
|
} catch (err) {
|
|
181
|
-
|
|
260
|
+
if (err.name === "TooOld") {
|
|
261
|
+
// Prevent any body events from firing
|
|
262
|
+
body.removeAllListeners();
|
|
263
|
+
|
|
264
|
+
// Reload the watch
|
|
265
|
+
void this.#errHandler(err);
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
this.#events.emit(WatchEvent.DATA_ERROR, err);
|
|
182
270
|
}
|
|
183
271
|
});
|
|
184
272
|
|
|
185
|
-
body
|
|
186
|
-
body.on("
|
|
187
|
-
body.on("
|
|
273
|
+
// Bind the body events
|
|
274
|
+
body.on("error", this.#errHandler);
|
|
275
|
+
body.on("close", this.#cleanup);
|
|
276
|
+
body.on("finish", this.#cleanup);
|
|
188
277
|
|
|
189
278
|
// Pipe the response body to the stream
|
|
190
|
-
body.pipe(stream);
|
|
279
|
+
body.pipe(this.#stream);
|
|
191
280
|
} else {
|
|
192
|
-
throw new Error(`watch failed: ${response.status} ${response.statusText}`);
|
|
281
|
+
throw new Error(`watch connect failed: ${response.status} ${response.statusText}`);
|
|
193
282
|
}
|
|
194
283
|
} catch (e) {
|
|
195
|
-
|
|
284
|
+
void this.#errHandler(e);
|
|
196
285
|
}
|
|
286
|
+
};
|
|
197
287
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
288
|
+
/**
|
|
289
|
+
* Resync the watch.
|
|
290
|
+
*
|
|
291
|
+
* @returns the error handler
|
|
292
|
+
*/
|
|
293
|
+
#resync = () =>
|
|
294
|
+
this.#errHandler({
|
|
295
|
+
name: "Resync",
|
|
296
|
+
message: "Resync triggered by resyncIntervalSec",
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
/** Clear the resync timer and schedule a new one. */
|
|
300
|
+
#scheduleResync = async () => {
|
|
301
|
+
clearTimeout(this.#resyncTimer);
|
|
302
|
+
this.#resyncTimer = setTimeout(this.#resync, this.#watchCfg.resyncIntervalSec! * 1000);
|
|
303
|
+
};
|
|
207
304
|
|
|
208
|
-
|
|
305
|
+
/**
|
|
306
|
+
* Update the resource version.
|
|
307
|
+
*
|
|
308
|
+
* @param resourceVersion - the new resource version
|
|
309
|
+
*/
|
|
310
|
+
#setResourceVersion = (resourceVersion?: string) => {
|
|
311
|
+
this.#watchCfg.resourceVersion = resourceVersion;
|
|
312
|
+
this.#events.emit(WatchEvent.RESOURCE_VERSION, resourceVersion);
|
|
313
|
+
};
|
|
209
314
|
|
|
210
|
-
|
|
211
|
-
|
|
315
|
+
/**
|
|
316
|
+
* Reload the watch after an error.
|
|
317
|
+
*
|
|
318
|
+
* @param err - the error that occurred
|
|
319
|
+
*/
|
|
320
|
+
#reconnect = async (err: Error) => {
|
|
321
|
+
// If there are more attempts, retry the watch (undefined is unlimited retries)
|
|
322
|
+
if (this.#watchCfg.retryMax === undefined || this.#watchCfg.retryMax > this.#retryCount) {
|
|
323
|
+
// Sleep for the specified delay, but check every 500ms if the watch has been aborted
|
|
324
|
+
let delay = this.#watchCfg.retryDelaySec! * 1000;
|
|
325
|
+
while (delay > 0) {
|
|
326
|
+
if (this.#abortController.signal.aborted) {
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
delay -= 500;
|
|
330
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
331
|
+
}
|
|
212
332
|
|
|
213
|
-
|
|
214
|
-
|
|
333
|
+
this.#retryCount++;
|
|
334
|
+
this.#events.emit(WatchEvent.RECONNECT, err, this.#retryCount);
|
|
335
|
+
|
|
336
|
+
if (this.#pendingReconnect) {
|
|
337
|
+
this.#events.emit(WatchEvent.RECONNECT_PENDING);
|
|
215
338
|
} else {
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
339
|
+
this.#pendingReconnect = true;
|
|
340
|
+
this.#cleanup();
|
|
341
|
+
|
|
342
|
+
// Retry the watch after the delay
|
|
343
|
+
await this.#runner();
|
|
220
344
|
}
|
|
345
|
+
} else {
|
|
346
|
+
// Otherwise, call the finally function if it exists
|
|
347
|
+
this.#events.emit(WatchEvent.GIVE_UP, err);
|
|
348
|
+
this.close();
|
|
221
349
|
}
|
|
222
|
-
}
|
|
350
|
+
};
|
|
223
351
|
|
|
224
|
-
|
|
352
|
+
/**
|
|
353
|
+
* Handle errors from the stream.
|
|
354
|
+
*
|
|
355
|
+
* @param err - the error that occurred
|
|
356
|
+
*/
|
|
357
|
+
#errHandler = async (err: Error) => {
|
|
358
|
+
switch (err.name) {
|
|
359
|
+
case "AbortError":
|
|
360
|
+
clearTimeout(this.#resyncTimer);
|
|
361
|
+
this.#cleanup();
|
|
362
|
+
this.#events.emit(WatchEvent.ABORT, err);
|
|
363
|
+
return;
|
|
364
|
+
|
|
365
|
+
case "TooOld":
|
|
366
|
+
// Purge the resource version if it is too old
|
|
367
|
+
this.#setResourceVersion(undefined);
|
|
368
|
+
this.#events.emit(WatchEvent.OLD_RESOURCE_VERSION, err.message);
|
|
369
|
+
break;
|
|
370
|
+
|
|
371
|
+
case "Resync":
|
|
372
|
+
this.#events.emit(WatchEvent.RESYNC, err);
|
|
373
|
+
break;
|
|
374
|
+
|
|
375
|
+
default:
|
|
376
|
+
this.#events.emit(WatchEvent.NETWORK_ERROR, err);
|
|
377
|
+
break;
|
|
378
|
+
}
|
|
225
379
|
|
|
226
|
-
|
|
380
|
+
await this.#reconnect(err);
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
/** Cleanup the stream and listeners. */
|
|
384
|
+
#cleanup = () => {
|
|
385
|
+
if (this.#stream) {
|
|
386
|
+
this.#stream.removeAllListeners();
|
|
387
|
+
this.#stream.destroy();
|
|
388
|
+
}
|
|
389
|
+
};
|
|
227
390
|
}
|
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
|
|