@xplane/utils 0.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/cli.d.mts +1 -0
- package/dist/cli.mjs +394 -0
- package/dist/cli.mjs.map +1 -0
- package/dist/index.d.mts +119 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +3 -0
- package/dist/render/index.d.mts +85 -0
- package/dist/render/index.d.mts.map +1 -0
- package/dist/render/index.mjs +2 -0
- package/dist/render-Cnhpyf1X.mjs +364 -0
- package/dist/render-Cnhpyf1X.mjs.map +1 -0
- package/dist/tree-DaHkojq8.mjs +96 -0
- package/dist/tree-DaHkojq8.mjs.map +1 -0
- package/dist/xr-watcher-1etyvp-6.mjs +532 -0
- package/dist/xr-watcher-1etyvp-6.mjs.map +1 -0
- package/dist/xr-watcher-EiWHVp3o.d.mts +135 -0
- package/dist/xr-watcher-EiWHVp3o.d.mts.map +1 -0
- package/package.json +51 -0
|
@@ -0,0 +1,532 @@
|
|
|
1
|
+
import { CoreV1Api, CustomObjectsApi, KubeConfig, Watch } from "@kubernetes/client-node";
|
|
2
|
+
//#region src/client/kubeconfig.ts
|
|
3
|
+
/**
|
|
4
|
+
* Load a `KubeConfig` from disk using `client-node`'s default rules (with
|
|
5
|
+
* optional explicit overrides). The returned config has its current context
|
|
6
|
+
* applied.
|
|
7
|
+
*/
|
|
8
|
+
function loadKubeConfig(opts = {}) {
|
|
9
|
+
const kc = new KubeConfig();
|
|
10
|
+
if (opts.kubeconfig) kc.loadFromFile(opts.kubeconfig);
|
|
11
|
+
else kc.loadFromDefault();
|
|
12
|
+
if (opts.context) kc.setCurrentContext(opts.context);
|
|
13
|
+
return kc;
|
|
14
|
+
}
|
|
15
|
+
//#endregion
|
|
16
|
+
//#region src/watcher/await-ready.ts
|
|
17
|
+
/**
|
|
18
|
+
* Resolve when the watcher observes its first `Ready=True` snapshot. Rejects on
|
|
19
|
+
* the watcher's first error, if the stream ends without becoming ready, or on
|
|
20
|
+
* `timeoutMs`.
|
|
21
|
+
*
|
|
22
|
+
* Uses the watcher's dedicated `ready` promise — it does NOT consume queue
|
|
23
|
+
* events, so it composes safely with a concurrent renderer iterating the
|
|
24
|
+
* watcher.
|
|
25
|
+
*
|
|
26
|
+
* The watcher itself is not stopped on resolution — call `watcher.stop()` from
|
|
27
|
+
* a `.finally()` if you want the underlying connections torn down.
|
|
28
|
+
*/
|
|
29
|
+
async function awaitReady(watcher, opts = {}) {
|
|
30
|
+
if (opts.timeoutMs === void 0) return watcher.ready;
|
|
31
|
+
let timer;
|
|
32
|
+
const timeout = new Promise((_, reject) => {
|
|
33
|
+
timer = setTimeout(() => reject(/* @__PURE__ */ new Error(`Timed out after ${opts.timeoutMs}ms waiting for Ready`)), opts.timeoutMs);
|
|
34
|
+
});
|
|
35
|
+
try {
|
|
36
|
+
return await Promise.race([watcher.ready, timeout]);
|
|
37
|
+
} finally {
|
|
38
|
+
if (timer) clearTimeout(timer);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
//#endregion
|
|
42
|
+
//#region src/watcher/readiness.ts
|
|
43
|
+
const isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
44
|
+
const asString = (v) => typeof v === "string" ? v : void 0;
|
|
45
|
+
/**
|
|
46
|
+
* Build an `XrSnapshot` from a raw Kubernetes object. Parses the `Ready` condition,
|
|
47
|
+
* the optional `status.xplane` payload and `status.resourceRefs`.
|
|
48
|
+
*/
|
|
49
|
+
function buildSnapshot(object) {
|
|
50
|
+
const status = isRecord(object.status) ? object.status : void 0;
|
|
51
|
+
const conditions = [];
|
|
52
|
+
if (status && Array.isArray(status.conditions)) {
|
|
53
|
+
for (const c of status.conditions) if (isRecord(c)) conditions.push({
|
|
54
|
+
type: asString(c.type),
|
|
55
|
+
status: asString(c.status),
|
|
56
|
+
reason: asString(c.reason),
|
|
57
|
+
message: asString(c.message)
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
const ready = conditions.find((c) => c.type === "Ready");
|
|
61
|
+
const responsive = conditions.find((c) => c.type === "Responsive");
|
|
62
|
+
const synced = conditions.find((c) => c.type === "Synced");
|
|
63
|
+
const snapshot = {
|
|
64
|
+
object,
|
|
65
|
+
ready: ready?.status === "True",
|
|
66
|
+
resourceRefs: parseResourceRefs(status)
|
|
67
|
+
};
|
|
68
|
+
if (ready?.reason !== void 0) snapshot.readyReason = ready.reason;
|
|
69
|
+
if (ready?.message !== void 0) snapshot.readyMessage = ready.message;
|
|
70
|
+
if (responsive?.status === "False" && responsive.reason === "WatchCircuitOpen") snapshot.updatesThrottled = true;
|
|
71
|
+
if (synced?.status === "False" && synced.reason === "ReconcileError") snapshot.syncError = {
|
|
72
|
+
reason: synced.reason,
|
|
73
|
+
message: synced.message ?? ""
|
|
74
|
+
};
|
|
75
|
+
const xplane = parseXplane(status);
|
|
76
|
+
if (xplane) snapshot.xplane = xplane;
|
|
77
|
+
return snapshot;
|
|
78
|
+
}
|
|
79
|
+
function parseResourceRefs(status) {
|
|
80
|
+
const out = [];
|
|
81
|
+
if (!status) return out;
|
|
82
|
+
const refs = status.resourceRefs;
|
|
83
|
+
if (!Array.isArray(refs)) return out;
|
|
84
|
+
for (const r of refs) {
|
|
85
|
+
if (!isRecord(r)) continue;
|
|
86
|
+
const apiVersion = asString(r.apiVersion) ?? "";
|
|
87
|
+
const kind = asString(r.kind) ?? "";
|
|
88
|
+
const name = asString(r.name) ?? "";
|
|
89
|
+
if (name) out.push({
|
|
90
|
+
apiVersion,
|
|
91
|
+
kind,
|
|
92
|
+
name
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
return out;
|
|
96
|
+
}
|
|
97
|
+
function parseXplane(status) {
|
|
98
|
+
if (!status || !isRecord(status.xplane)) return void 0;
|
|
99
|
+
const x = status.xplane;
|
|
100
|
+
const emitted = [];
|
|
101
|
+
if (Array.isArray(x.emittedResources)) for (const r of x.emittedResources) {
|
|
102
|
+
if (!isRecord(r)) continue;
|
|
103
|
+
const nodePath = asString(r.nodePath);
|
|
104
|
+
if (!nodePath) continue;
|
|
105
|
+
const name = asString(r.name);
|
|
106
|
+
const namespace = asString(r.namespace);
|
|
107
|
+
emitted.push({
|
|
108
|
+
apiVersion: asString(r.apiVersion) ?? "",
|
|
109
|
+
kind: asString(r.kind) ?? "",
|
|
110
|
+
nodePath,
|
|
111
|
+
...name ? { name } : {},
|
|
112
|
+
...namespace ? { namespace } : {},
|
|
113
|
+
ready: r.ready === true
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
const blocked = [];
|
|
117
|
+
if (Array.isArray(x.blockedResources)) for (const r of x.blockedResources) {
|
|
118
|
+
if (!isRecord(r)) continue;
|
|
119
|
+
const nodePath = asString(r.nodePath);
|
|
120
|
+
if (!nodePath) continue;
|
|
121
|
+
const name = asString(r.name);
|
|
122
|
+
const namespace = asString(r.namespace);
|
|
123
|
+
const entry = {
|
|
124
|
+
apiVersion: asString(r.apiVersion) ?? "",
|
|
125
|
+
kind: asString(r.kind) ?? "",
|
|
126
|
+
nodePath,
|
|
127
|
+
...name ? { name } : {},
|
|
128
|
+
...namespace ? { namespace } : {}
|
|
129
|
+
};
|
|
130
|
+
if (Array.isArray(r.waitingFor)) {
|
|
131
|
+
const wf = r.waitingFor.filter((s) => typeof s === "string");
|
|
132
|
+
if (wf.length > 0) entry.waitingFor = wf;
|
|
133
|
+
}
|
|
134
|
+
blocked.push(entry);
|
|
135
|
+
}
|
|
136
|
+
return {
|
|
137
|
+
emittedResources: emitted,
|
|
138
|
+
blockedResources: blocked
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
//#endregion
|
|
142
|
+
//#region src/watcher/target.ts
|
|
143
|
+
function parseTarget(input) {
|
|
144
|
+
const slash = input.indexOf("/");
|
|
145
|
+
if (slash < 0) throw new Error(`Invalid target "${input}": expected kubectl-style "<resource>/<name>" (e.g. "xprojects/foo" or "xprojects.platform.example.com/foo").`);
|
|
146
|
+
const left = input.slice(0, slash);
|
|
147
|
+
const name = input.slice(slash + 1);
|
|
148
|
+
if (left.length === 0 || name.length === 0) throw new Error(`Invalid target "${input}": both resource and name are required.`);
|
|
149
|
+
const dot = left.indexOf(".");
|
|
150
|
+
if (dot < 0) return {
|
|
151
|
+
resource: left,
|
|
152
|
+
name
|
|
153
|
+
};
|
|
154
|
+
const resource = left.slice(0, dot);
|
|
155
|
+
const rest = left.slice(dot + 1);
|
|
156
|
+
const restDot = rest.indexOf(".");
|
|
157
|
+
if (restDot > 0) {
|
|
158
|
+
const maybeVersion = rest.slice(0, restDot);
|
|
159
|
+
if (/^v\d/.test(maybeVersion)) return {
|
|
160
|
+
resource,
|
|
161
|
+
version: maybeVersion,
|
|
162
|
+
group: rest.slice(restDot + 1),
|
|
163
|
+
name
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
return {
|
|
167
|
+
resource,
|
|
168
|
+
group: rest,
|
|
169
|
+
name
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
//#endregion
|
|
173
|
+
//#region src/client/lists.ts
|
|
174
|
+
/** Initial-list for a single XR via `CustomObjectsApi`, filtered by name. */
|
|
175
|
+
async function listXrCollection(kc, ref) {
|
|
176
|
+
const api = kc.makeApiClient(CustomObjectsApi);
|
|
177
|
+
const fieldSelector = `metadata.name=${ref.name}`;
|
|
178
|
+
const res = await (ref.namespaced && ref.namespace ? api.listNamespacedCustomObject({
|
|
179
|
+
group: ref.group,
|
|
180
|
+
version: ref.version,
|
|
181
|
+
namespace: ref.namespace,
|
|
182
|
+
plural: ref.plural,
|
|
183
|
+
fieldSelector
|
|
184
|
+
}) : api.listClusterCustomObject({
|
|
185
|
+
group: ref.group,
|
|
186
|
+
version: ref.version,
|
|
187
|
+
plural: ref.plural,
|
|
188
|
+
fieldSelector
|
|
189
|
+
}));
|
|
190
|
+
return {
|
|
191
|
+
resourceVersion: res.metadata?.resourceVersion ?? "",
|
|
192
|
+
items: res.items ?? []
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
/** Initial-list of Kubernetes Events scoped to a namespace + field selector. */
|
|
196
|
+
async function listXrEvents(kc, namespace, fieldSelector) {
|
|
197
|
+
const res = await kc.makeApiClient(CoreV1Api).listNamespacedEvent({
|
|
198
|
+
namespace,
|
|
199
|
+
fieldSelector
|
|
200
|
+
});
|
|
201
|
+
return {
|
|
202
|
+
resourceVersion: res.metadata?.resourceVersion ?? "",
|
|
203
|
+
items: res.items ?? []
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
//#endregion
|
|
207
|
+
//#region src/client/watch.ts
|
|
208
|
+
/**
|
|
209
|
+
* Run a robust list-then-watch loop. The initial list is supplied by the caller
|
|
210
|
+
* (so it can use a typed Kubernetes client); the watch stream uses the raw
|
|
211
|
+
* `Watch` class because it is the only API surface that supports a
|
|
212
|
+
* `fieldSelector` for a single named object.
|
|
213
|
+
*
|
|
214
|
+
* Behaviour:
|
|
215
|
+
* 1. Call `opts.list()` and emit each item as `ADDED`.
|
|
216
|
+
* 2. Start a `Watch` stream from the returned `resourceVersion`. Each event is forwarded.
|
|
217
|
+
* 3. When the stream ends with an error or the server closes it, sleep briefly and reconnect.
|
|
218
|
+
* 4. The loop exits cleanly when the supplied `AbortSignal` fires.
|
|
219
|
+
*/
|
|
220
|
+
async function listAndWatch(kc, opts) {
|
|
221
|
+
const state = { resourceVersion: "" };
|
|
222
|
+
const watcher = new Watch(kc);
|
|
223
|
+
while (!opts.signal.aborted) try {
|
|
224
|
+
const list = await opts.list();
|
|
225
|
+
state.resourceVersion = list.resourceVersion;
|
|
226
|
+
for (const item of list.items) opts.onEvent("ADDED", item);
|
|
227
|
+
await runWatchOnce(watcher, opts, state);
|
|
228
|
+
} catch (err) {
|
|
229
|
+
if (opts.signal.aborted) return;
|
|
230
|
+
const e = err instanceof Error ? err : new Error(String(err));
|
|
231
|
+
opts.onError?.(e);
|
|
232
|
+
await sleep(1e3, opts.signal);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
function runWatchOnce(watcher, opts, state) {
|
|
236
|
+
return new Promise((resolve, reject) => {
|
|
237
|
+
const queryParams = { watch: true };
|
|
238
|
+
if (opts.fieldSelector) queryParams.fieldSelector = opts.fieldSelector;
|
|
239
|
+
if (opts.labelSelector) queryParams.labelSelector = opts.labelSelector;
|
|
240
|
+
if (state.resourceVersion) queryParams.resourceVersion = state.resourceVersion;
|
|
241
|
+
queryParams.allowWatchBookmarks = true;
|
|
242
|
+
let controllerRef;
|
|
243
|
+
let settled = false;
|
|
244
|
+
const resolveOnce = () => {
|
|
245
|
+
if (settled) return;
|
|
246
|
+
settled = true;
|
|
247
|
+
opts.signal.removeEventListener("abort", onAbort);
|
|
248
|
+
resolve();
|
|
249
|
+
};
|
|
250
|
+
const rejectOnce = (err) => {
|
|
251
|
+
if (settled) return;
|
|
252
|
+
settled = true;
|
|
253
|
+
opts.signal.removeEventListener("abort", onAbort);
|
|
254
|
+
reject(err instanceof Error ? err : new Error(String(err)));
|
|
255
|
+
};
|
|
256
|
+
const onAbort = () => {
|
|
257
|
+
controllerRef?.abort();
|
|
258
|
+
resolveOnce();
|
|
259
|
+
};
|
|
260
|
+
opts.signal.addEventListener("abort", onAbort, { once: true });
|
|
261
|
+
watcher.watch(opts.path, queryParams, (phase, apiObj) => {
|
|
262
|
+
const obj = apiObj;
|
|
263
|
+
const objRv = obj.metadata?.resourceVersion ?? "";
|
|
264
|
+
if (objRv) state.resourceVersion = objRv;
|
|
265
|
+
opts.onEvent(phase, obj);
|
|
266
|
+
}, (err) => {
|
|
267
|
+
if (err) rejectOnce(err);
|
|
268
|
+
else resolveOnce();
|
|
269
|
+
}).then((controller) => {
|
|
270
|
+
controllerRef = controller;
|
|
271
|
+
if (opts.signal.aborted) {
|
|
272
|
+
controller.abort();
|
|
273
|
+
resolveOnce();
|
|
274
|
+
}
|
|
275
|
+
}).catch((err) => rejectOnce(err));
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
function sleep(ms, signal) {
|
|
279
|
+
return new Promise((resolve) => {
|
|
280
|
+
if (signal.aborted) {
|
|
281
|
+
resolve();
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
const t = setTimeout(() => {
|
|
285
|
+
signal.removeEventListener("abort", onAbort);
|
|
286
|
+
resolve();
|
|
287
|
+
}, ms);
|
|
288
|
+
const onAbort = () => {
|
|
289
|
+
clearTimeout(t);
|
|
290
|
+
resolve();
|
|
291
|
+
};
|
|
292
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
//#endregion
|
|
296
|
+
//#region src/watcher/queue.ts
|
|
297
|
+
/**
|
|
298
|
+
* Single-consumer async queue used by the XR watcher to expose its event
|
|
299
|
+
* stream as both a callback API and a `for await` async iterable.
|
|
300
|
+
*
|
|
301
|
+
* Memory-bounded only by the producer. Designed for low-frequency control-plane
|
|
302
|
+
* events (Kubernetes watch + status reconciliations), not high-throughput data.
|
|
303
|
+
*/
|
|
304
|
+
var AsyncQueue = class {
|
|
305
|
+
buffer = [];
|
|
306
|
+
waiters = [];
|
|
307
|
+
closed = false;
|
|
308
|
+
push(value) {
|
|
309
|
+
if (this.closed) return;
|
|
310
|
+
const waiter = this.waiters.shift();
|
|
311
|
+
if (waiter) waiter({
|
|
312
|
+
value,
|
|
313
|
+
done: false
|
|
314
|
+
});
|
|
315
|
+
else this.buffer.push(value);
|
|
316
|
+
}
|
|
317
|
+
close() {
|
|
318
|
+
if (this.closed) return;
|
|
319
|
+
this.closed = true;
|
|
320
|
+
for (const w of this.waiters.splice(0)) w({
|
|
321
|
+
value: void 0,
|
|
322
|
+
done: true
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
[Symbol.asyncIterator]() {
|
|
326
|
+
return {
|
|
327
|
+
next: () => {
|
|
328
|
+
const v = this.buffer.shift();
|
|
329
|
+
if (v !== void 0) return Promise.resolve({
|
|
330
|
+
value: v,
|
|
331
|
+
done: false
|
|
332
|
+
});
|
|
333
|
+
if (this.closed) return Promise.resolve({
|
|
334
|
+
value: void 0,
|
|
335
|
+
done: true
|
|
336
|
+
});
|
|
337
|
+
return new Promise((resolve) => this.waiters.push(resolve));
|
|
338
|
+
},
|
|
339
|
+
return: () => {
|
|
340
|
+
this.close();
|
|
341
|
+
return Promise.resolve({
|
|
342
|
+
value: void 0,
|
|
343
|
+
done: true
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
};
|
|
349
|
+
//#endregion
|
|
350
|
+
//#region src/watcher/xr-watcher.ts
|
|
351
|
+
/**
|
|
352
|
+
* Subscribe to live updates of a single XR. Emits a `snapshot` on every observed
|
|
353
|
+
* change, a one-shot `ready` when the XR's `Ready` condition first becomes True,
|
|
354
|
+
* and (unless disabled) `k8s-event` items for Kubernetes Events targeting the XR.
|
|
355
|
+
*
|
|
356
|
+
* The watcher uses list-then-watch with auto-reconnect — no polling.
|
|
357
|
+
*/
|
|
358
|
+
function createXrWatcher(opts) {
|
|
359
|
+
const queue = new AsyncQueue();
|
|
360
|
+
const controller = new AbortController();
|
|
361
|
+
const userSignal = opts.signal;
|
|
362
|
+
if (userSignal) if (userSignal.aborted) controller.abort();
|
|
363
|
+
else userSignal.addEventListener("abort", () => controller.abort(), { once: true });
|
|
364
|
+
let readyEmitted = false;
|
|
365
|
+
let uid;
|
|
366
|
+
let eventsStarted = false;
|
|
367
|
+
let eventsTask = Promise.resolve();
|
|
368
|
+
const seenEventUids = /* @__PURE__ */ new Set();
|
|
369
|
+
let resolveReady;
|
|
370
|
+
let rejectReady;
|
|
371
|
+
let readySettled = false;
|
|
372
|
+
const ready = new Promise((res, rej) => {
|
|
373
|
+
resolveReady = (s) => {
|
|
374
|
+
if (readySettled) return;
|
|
375
|
+
readySettled = true;
|
|
376
|
+
res(s);
|
|
377
|
+
};
|
|
378
|
+
rejectReady = (e) => {
|
|
379
|
+
if (readySettled) return;
|
|
380
|
+
readySettled = true;
|
|
381
|
+
rej(e);
|
|
382
|
+
};
|
|
383
|
+
});
|
|
384
|
+
ready.catch(() => void 0);
|
|
385
|
+
const handleXrEvent = (phase, obj) => {
|
|
386
|
+
if (phase === "DELETED") {
|
|
387
|
+
const err = /* @__PURE__ */ new Error(`XR ${opts.ref.kind}/${opts.ref.name} was deleted`);
|
|
388
|
+
queue.push({
|
|
389
|
+
type: "error",
|
|
390
|
+
error: err
|
|
391
|
+
});
|
|
392
|
+
rejectReady(err);
|
|
393
|
+
controller.abort();
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
if (phase === "BOOKMARK" || phase === "ERROR") return;
|
|
397
|
+
const snapshot = buildSnapshot(obj);
|
|
398
|
+
queue.push({
|
|
399
|
+
type: "snapshot",
|
|
400
|
+
snapshot
|
|
401
|
+
});
|
|
402
|
+
if (snapshot.syncError) {
|
|
403
|
+
const err = /* @__PURE__ */ new Error(`XR ${opts.ref.kind}/${opts.ref.name} reconcile failed: ${snapshot.syncError.message || snapshot.syncError.reason}`);
|
|
404
|
+
queue.push({
|
|
405
|
+
type: "error",
|
|
406
|
+
error: err
|
|
407
|
+
});
|
|
408
|
+
rejectReady(err);
|
|
409
|
+
controller.abort();
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
if (!readyEmitted && snapshot.ready) {
|
|
413
|
+
readyEmitted = true;
|
|
414
|
+
queue.push({
|
|
415
|
+
type: "ready",
|
|
416
|
+
snapshot
|
|
417
|
+
});
|
|
418
|
+
resolveReady(snapshot);
|
|
419
|
+
}
|
|
420
|
+
const newUid = obj.metadata?.uid;
|
|
421
|
+
if (newUid && !uid) {
|
|
422
|
+
uid = newUid;
|
|
423
|
+
if (!opts.disableEvents && opts.ref.namespaced && opts.ref.namespace) {
|
|
424
|
+
eventsStarted = true;
|
|
425
|
+
eventsTask = startEventsWatch(opts, uid, controller.signal, queue, seenEventUids);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
};
|
|
429
|
+
const xrPath = buildResourcePath(opts.ref);
|
|
430
|
+
let firstListSeenItem = false;
|
|
431
|
+
const xrTask = listAndWatch(opts.kubeConfig, {
|
|
432
|
+
path: xrPath,
|
|
433
|
+
fieldSelector: `metadata.name=${opts.ref.name}`,
|
|
434
|
+
signal: controller.signal,
|
|
435
|
+
list: async () => {
|
|
436
|
+
const result = await listXrCollection(opts.kubeConfig, opts.ref);
|
|
437
|
+
if (!firstListSeenItem) if (result.items.length > 0) firstListSeenItem = true;
|
|
438
|
+
else {
|
|
439
|
+
const where = opts.ref.namespace ? ` in namespace ${opts.ref.namespace}` : "";
|
|
440
|
+
const err = /* @__PURE__ */ new Error(`XR ${opts.ref.kind}/${opts.ref.name} not found${where}`);
|
|
441
|
+
queue.push({
|
|
442
|
+
type: "error",
|
|
443
|
+
error: err
|
|
444
|
+
});
|
|
445
|
+
rejectReady(err);
|
|
446
|
+
controller.abort();
|
|
447
|
+
}
|
|
448
|
+
return result;
|
|
449
|
+
},
|
|
450
|
+
onEvent: handleXrEvent,
|
|
451
|
+
onError: (err) => {
|
|
452
|
+
queue.push({
|
|
453
|
+
type: "error",
|
|
454
|
+
error: err
|
|
455
|
+
});
|
|
456
|
+
rejectReady(err);
|
|
457
|
+
}
|
|
458
|
+
});
|
|
459
|
+
return {
|
|
460
|
+
ready,
|
|
461
|
+
done: (async () => {
|
|
462
|
+
try {
|
|
463
|
+
await xrTask;
|
|
464
|
+
} finally {
|
|
465
|
+
if (eventsStarted) try {
|
|
466
|
+
await eventsTask;
|
|
467
|
+
} catch {}
|
|
468
|
+
rejectReady(/* @__PURE__ */ new Error("Watcher ended before XR became Ready"));
|
|
469
|
+
queue.push({ type: "end" });
|
|
470
|
+
queue.close();
|
|
471
|
+
}
|
|
472
|
+
})(),
|
|
473
|
+
stop: () => controller.abort(),
|
|
474
|
+
[Symbol.asyncIterator]: () => queue[Symbol.asyncIterator]()
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
function buildResourcePath(ref) {
|
|
478
|
+
const base = ref.group ? `/apis/${ref.group}/${ref.version}` : `/api/${ref.version}`;
|
|
479
|
+
if (ref.namespaced) {
|
|
480
|
+
if (!ref.namespace) throw new Error(`Namespace is required for namespaced resource ${ref.kind}/${ref.name}`);
|
|
481
|
+
return `${base}/namespaces/${ref.namespace}/${ref.plural}`;
|
|
482
|
+
}
|
|
483
|
+
return `${base}/${ref.plural}`;
|
|
484
|
+
}
|
|
485
|
+
async function startEventsWatch(opts, uid, signal, queue, seen) {
|
|
486
|
+
if (!opts.ref.namespace) return;
|
|
487
|
+
const namespace = opts.ref.namespace;
|
|
488
|
+
const path = `/api/v1/namespaces/${namespace}/events`;
|
|
489
|
+
const fieldSelector = `involvedObject.uid=${uid}`;
|
|
490
|
+
await listAndWatch(opts.kubeConfig, {
|
|
491
|
+
path,
|
|
492
|
+
fieldSelector,
|
|
493
|
+
signal,
|
|
494
|
+
list: () => listXrEvents(opts.kubeConfig, namespace, fieldSelector),
|
|
495
|
+
onEvent: (phase, obj) => {
|
|
496
|
+
if (phase !== "ADDED" && phase !== "MODIFIED") return;
|
|
497
|
+
const eventUid = obj.metadata?.uid;
|
|
498
|
+
if (eventUid && seen.has(eventUid)) return;
|
|
499
|
+
if (eventUid) seen.add(eventUid);
|
|
500
|
+
const event = toKubernetesEvent(obj);
|
|
501
|
+
if (event) queue.push({
|
|
502
|
+
type: "k8s-event",
|
|
503
|
+
event
|
|
504
|
+
});
|
|
505
|
+
},
|
|
506
|
+
onError: (err) => queue.push({
|
|
507
|
+
type: "error",
|
|
508
|
+
error: err
|
|
509
|
+
})
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
function toKubernetesEvent(obj) {
|
|
513
|
+
const raw = obj;
|
|
514
|
+
if (!raw.message && !raw.reason) return void 0;
|
|
515
|
+
const event = {
|
|
516
|
+
type: raw.type ?? "Normal",
|
|
517
|
+
reason: raw.reason ?? "",
|
|
518
|
+
message: raw.message ?? "",
|
|
519
|
+
count: raw.count ?? 1
|
|
520
|
+
};
|
|
521
|
+
const first = raw.firstTimestamp ?? raw.eventTime;
|
|
522
|
+
const last = raw.lastTimestamp ?? raw.eventTime ?? first;
|
|
523
|
+
if (first) event.firstTimestamp = first;
|
|
524
|
+
if (last) event.lastTimestamp = last;
|
|
525
|
+
if (raw.involvedObject?.kind) event.involvedKind = raw.involvedObject.kind;
|
|
526
|
+
if (raw.involvedObject?.name) event.involvedName = raw.involvedObject.name;
|
|
527
|
+
return event;
|
|
528
|
+
}
|
|
529
|
+
//#endregion
|
|
530
|
+
export { awaitReady as a, buildSnapshot as i, listXrCollection as n, loadKubeConfig as o, parseTarget as r, createXrWatcher as t };
|
|
531
|
+
|
|
532
|
+
//# sourceMappingURL=xr-watcher-1etyvp-6.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"xr-watcher-1etyvp-6.mjs","names":[],"sources":["../src/client/kubeconfig.ts","../src/watcher/await-ready.ts","../src/watcher/readiness.ts","../src/watcher/target.ts","../src/client/lists.ts","../src/client/watch.ts","../src/watcher/queue.ts","../src/watcher/xr-watcher.ts"],"sourcesContent":["import { KubeConfig } from '@kubernetes/client-node';\n\nexport interface LoadKubeConfigOptions {\n /** Explicit kubeconfig file path. Falls back to `KUBECONFIG` / default discovery. */\n kubeconfig?: string;\n /** Override the active context. */\n context?: string;\n}\n\n/**\n * Load a `KubeConfig` from disk using `client-node`'s default rules (with\n * optional explicit overrides). The returned config has its current context\n * applied.\n */\nexport function loadKubeConfig(opts: LoadKubeConfigOptions = {}): KubeConfig {\n const kc = new KubeConfig();\n if (opts.kubeconfig) {\n kc.loadFromFile(opts.kubeconfig);\n } else {\n kc.loadFromDefault();\n }\n if (opts.context) {\n kc.setCurrentContext(opts.context);\n }\n return kc;\n}\n","import type { XrSnapshot } from './types.js';\nimport type { XrWatcher } from './xr-watcher.js';\n\nexport interface AwaitReadyOptions {\n /** Maximum time in ms to wait for `Ready=True` before rejecting. */\n timeoutMs?: number;\n}\n\n/**\n * Resolve when the watcher observes its first `Ready=True` snapshot. Rejects on\n * the watcher's first error, if the stream ends without becoming ready, or on\n * `timeoutMs`.\n *\n * Uses the watcher's dedicated `ready` promise — it does NOT consume queue\n * events, so it composes safely with a concurrent renderer iterating the\n * watcher.\n *\n * The watcher itself is not stopped on resolution — call `watcher.stop()` from\n * a `.finally()` if you want the underlying connections torn down.\n */\nexport async function awaitReady(\n watcher: XrWatcher,\n opts: AwaitReadyOptions = {},\n): Promise<XrSnapshot> {\n if (opts.timeoutMs === undefined) return watcher.ready;\n let timer: NodeJS.Timeout | undefined;\n const timeout = new Promise<never>((_, reject) => {\n timer = setTimeout(\n () => reject(new Error(`Timed out after ${opts.timeoutMs}ms waiting for Ready`)),\n opts.timeoutMs,\n );\n });\n try {\n return await Promise.race([watcher.ready, timeout]);\n } finally {\n if (timer) clearTimeout(timer);\n }\n}\n","import type { KubernetesObject } from '@kubernetes/client-node';\nimport type { ResourceRef, XplaneStatus, XrSnapshot } from './types.js';\n\ninterface Condition {\n type?: string;\n status?: string;\n reason?: string;\n message?: string;\n}\n\nconst isRecord = (v: unknown): v is Record<string, unknown> =>\n typeof v === 'object' && v !== null && !Array.isArray(v);\n\nconst asString = (v: unknown): string | undefined => (typeof v === 'string' ? v : undefined);\n\n/**\n * Build an `XrSnapshot` from a raw Kubernetes object. Parses the `Ready` condition,\n * the optional `status.xplane` payload and `status.resourceRefs`.\n */\nexport function buildSnapshot(object: KubernetesObject): XrSnapshot {\n const status = isRecord((object as { status?: unknown }).status)\n ? (object as { status: Record<string, unknown> }).status\n : undefined;\n\n const conditions: Condition[] = [];\n if (status && Array.isArray(status.conditions)) {\n for (const c of status.conditions as unknown[]) {\n if (isRecord(c)) {\n conditions.push({\n type: asString(c.type),\n status: asString(c.status),\n reason: asString(c.reason),\n message: asString(c.message),\n });\n }\n }\n }\n const ready = conditions.find((c) => c.type === 'Ready');\n const responsive = conditions.find((c) => c.type === 'Responsive');\n const synced = conditions.find((c) => c.type === 'Synced');\n\n const snapshot: XrSnapshot = {\n object,\n ready: ready?.status === 'True',\n resourceRefs: parseResourceRefs(status),\n };\n if (ready?.reason !== undefined) snapshot.readyReason = ready.reason;\n if (ready?.message !== undefined) snapshot.readyMessage = ready.message;\n if (responsive?.status === 'False' && responsive.reason === 'WatchCircuitOpen') {\n snapshot.updatesThrottled = true;\n }\n if (synced?.status === 'False' && synced.reason === 'ReconcileError') {\n snapshot.syncError = {\n reason: synced.reason,\n message: synced.message ?? '',\n };\n }\n\n const xplane = parseXplane(status);\n if (xplane) snapshot.xplane = xplane;\n\n return snapshot;\n}\n\nfunction parseResourceRefs(status: Record<string, unknown> | undefined): ResourceRef[] {\n const out: ResourceRef[] = [];\n if (!status) return out;\n const refs = status.resourceRefs;\n if (!Array.isArray(refs)) return out;\n for (const r of refs as unknown[]) {\n if (!isRecord(r)) continue;\n const apiVersion = asString(r.apiVersion) ?? '';\n const kind = asString(r.kind) ?? '';\n const name = asString(r.name) ?? '';\n if (name) out.push({ apiVersion, kind, name });\n }\n return out;\n}\n\nfunction parseXplane(status: Record<string, unknown> | undefined): XplaneStatus | undefined {\n if (!status || !isRecord(status.xplane)) return undefined;\n const x = status.xplane;\n const emitted: XplaneStatus['emittedResources'] = [];\n if (Array.isArray(x.emittedResources)) {\n for (const r of x.emittedResources as unknown[]) {\n if (!isRecord(r)) continue;\n const nodePath = asString(r.nodePath);\n if (!nodePath) continue;\n const name = asString(r.name);\n const namespace = asString(r.namespace);\n emitted.push({\n apiVersion: asString(r.apiVersion) ?? '',\n kind: asString(r.kind) ?? '',\n nodePath,\n ...(name ? { name } : {}),\n ...(namespace ? { namespace } : {}),\n ready: r.ready === true,\n });\n }\n }\n const blocked: XplaneStatus['blockedResources'] = [];\n if (Array.isArray(x.blockedResources)) {\n for (const r of x.blockedResources as unknown[]) {\n if (!isRecord(r)) continue;\n const nodePath = asString(r.nodePath);\n if (!nodePath) continue;\n const name = asString(r.name);\n const namespace = asString(r.namespace);\n const entry: XplaneStatus['blockedResources'][number] = {\n apiVersion: asString(r.apiVersion) ?? '',\n kind: asString(r.kind) ?? '',\n nodePath,\n ...(name ? { name } : {}),\n ...(namespace ? { namespace } : {}),\n };\n if (Array.isArray(r.waitingFor)) {\n const wf = (r.waitingFor as unknown[]).filter((s): s is string => typeof s === 'string');\n if (wf.length > 0) entry.waitingFor = wf;\n }\n blocked.push(entry);\n }\n }\n return { emittedResources: emitted, blockedResources: blocked };\n}\n","/**\n * Parse a kubectl-style resource target of the form:\n * <resource>/<name>\n * where `<resource>` is one of:\n * - `kind` (e.g. `xprojects`, `XProject`)\n * - `kind.group` (e.g. `xprojects.platform.example.com`)\n * - `kind.version.group` (e.g. `xprojects.v1alpha1.platform.example.com`)\n *\n * The `kind` token is returned as-is; resolving it to an API group/version/plural\n * is the discovery layer's responsibility (see `client/discovery.ts`).\n */\nexport interface ParsedTarget {\n /** Resource hint: kind, plural, or short name as typed by the user. */\n resource: string;\n /** Optional API group hint extracted from `kind.group` or `kind.version.group`. */\n group?: string;\n /** Optional API version hint extracted from `kind.version.group`. */\n version?: string;\n /** Object name. */\n name: string;\n}\n\nexport function parseTarget(input: string): ParsedTarget {\n const slash = input.indexOf('/');\n if (slash < 0) {\n throw new Error(\n `Invalid target \"${input}\": expected kubectl-style \"<resource>/<name>\" (e.g. \"xprojects/foo\" or \"xprojects.platform.example.com/foo\").`,\n );\n }\n const left = input.slice(0, slash);\n const name = input.slice(slash + 1);\n if (left.length === 0 || name.length === 0) {\n throw new Error(`Invalid target \"${input}\": both resource and name are required.`);\n }\n\n const dot = left.indexOf('.');\n if (dot < 0) return { resource: left, name };\n\n const resource = left.slice(0, dot);\n const rest = left.slice(dot + 1);\n\n // Heuristic: a Kubernetes API version token starts with `v` followed by a digit\n // (`v1`, `v1alpha1`, `v2beta3`). If the first segment of `rest` matches, treat\n // it as the version and the remainder as the group.\n const restDot = rest.indexOf('.');\n if (restDot > 0) {\n const maybeVersion = rest.slice(0, restDot);\n if (/^v\\d/.test(maybeVersion)) {\n return { resource, version: maybeVersion, group: rest.slice(restDot + 1), name };\n }\n }\n return { resource, group: rest, name };\n}\n","import {\n CoreV1Api,\n CustomObjectsApi,\n type KubeConfig,\n type KubernetesObject,\n} from '@kubernetes/client-node';\nimport type { XrRef } from '../watcher/types.js';\nimport type { ListResult } from './watch.js';\n\ninterface CustomObjectList {\n metadata?: { resourceVersion?: string };\n items?: KubernetesObject[];\n}\n\n/** Initial-list for a single XR via `CustomObjectsApi`, filtered by name. */\nexport async function listXrCollection(kc: KubeConfig, ref: XrRef): Promise<ListResult> {\n const api = kc.makeApiClient(CustomObjectsApi);\n const fieldSelector = `metadata.name=${ref.name}`;\n const res = (await (ref.namespaced && ref.namespace\n ? api.listNamespacedCustomObject({\n group: ref.group,\n version: ref.version,\n namespace: ref.namespace,\n plural: ref.plural,\n fieldSelector,\n })\n : api.listClusterCustomObject({\n group: ref.group,\n version: ref.version,\n plural: ref.plural,\n fieldSelector,\n }))) as CustomObjectList;\n return {\n resourceVersion: res.metadata?.resourceVersion ?? '',\n items: res.items ?? [],\n };\n}\n\n/** Initial-list of Kubernetes Events scoped to a namespace + field selector. */\nexport async function listXrEvents(\n kc: KubeConfig,\n namespace: string,\n fieldSelector: string,\n): Promise<ListResult> {\n const api = kc.makeApiClient(CoreV1Api);\n const res = await api.listNamespacedEvent({ namespace, fieldSelector });\n return {\n resourceVersion: res.metadata?.resourceVersion ?? '',\n items: (res.items ?? []) as unknown as KubernetesObject[],\n };\n}\n","import type { KubeConfig, KubernetesObject } from '@kubernetes/client-node';\nimport { Watch } from '@kubernetes/client-node';\n\n/** Watch event phase as published by the Kubernetes watch protocol. */\nexport type WatchPhase = 'ADDED' | 'MODIFIED' | 'DELETED' | 'BOOKMARK' | 'ERROR';\n\n/** Result of an initial list call. */\nexport interface ListResult<T extends KubernetesObject = KubernetesObject> {\n resourceVersion: string;\n items: T[];\n}\n\nexport interface ListWatchOptions {\n /** Watch URL path, e.g. `/apis/group/v1/namespaces/ns/plural`. */\n path: string;\n /** Optional field selector applied to the watch stream. */\n fieldSelector?: string;\n /** Optional label selector applied to the watch stream. */\n labelSelector?: string;\n /** Aborts both the initial list and any subsequent watch streams. */\n signal: AbortSignal;\n /** Caller-provided initial list. Use a typed client (`CoreV1Api`, `CustomObjectsApi`, …). */\n list: () => Promise<ListResult>;\n /** Invoked for each observed object (after the initial list and on each watch event). */\n onEvent: (phase: WatchPhase, obj: KubernetesObject) => void;\n /** Invoked when a stream ends in error and a reconnect is about to be attempted. */\n onError?: (err: Error) => void;\n}\n\ninterface ReconnectableState {\n resourceVersion: string;\n}\n\n/**\n * Run a robust list-then-watch loop. The initial list is supplied by the caller\n * (so it can use a typed Kubernetes client); the watch stream uses the raw\n * `Watch` class because it is the only API surface that supports a\n * `fieldSelector` for a single named object.\n *\n * Behaviour:\n * 1. Call `opts.list()` and emit each item as `ADDED`.\n * 2. Start a `Watch` stream from the returned `resourceVersion`. Each event is forwarded.\n * 3. When the stream ends with an error or the server closes it, sleep briefly and reconnect.\n * 4. The loop exits cleanly when the supplied `AbortSignal` fires.\n */\nexport async function listAndWatch(kc: KubeConfig, opts: ListWatchOptions): Promise<void> {\n const state: ReconnectableState = { resourceVersion: '' };\n const watcher = new Watch(kc);\n\n while (!opts.signal.aborted) {\n try {\n const list = await opts.list();\n state.resourceVersion = list.resourceVersion;\n for (const item of list.items) opts.onEvent('ADDED', item);\n\n await runWatchOnce(watcher, opts, state);\n } catch (err) {\n if (opts.signal.aborted) return;\n const e = err instanceof Error ? err : new Error(String(err));\n opts.onError?.(e);\n await sleep(1000, opts.signal);\n }\n }\n}\n\nfunction runWatchOnce(\n watcher: Watch,\n opts: ListWatchOptions,\n state: ReconnectableState,\n): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n const queryParams: Record<string, string | boolean> = { watch: true };\n if (opts.fieldSelector) queryParams.fieldSelector = opts.fieldSelector;\n if (opts.labelSelector) queryParams.labelSelector = opts.labelSelector;\n if (state.resourceVersion) queryParams.resourceVersion = state.resourceVersion;\n queryParams.allowWatchBookmarks = true;\n\n let controllerRef: AbortController | undefined;\n let settled = false;\n const resolveOnce = () => {\n if (settled) return;\n settled = true;\n opts.signal.removeEventListener('abort', onAbort);\n resolve();\n };\n const rejectOnce = (err: unknown) => {\n if (settled) return;\n settled = true;\n opts.signal.removeEventListener('abort', onAbort);\n reject(err instanceof Error ? err : new Error(String(err)));\n };\n const onAbort = () => {\n controllerRef?.abort();\n resolveOnce();\n };\n opts.signal.addEventListener('abort', onAbort, { once: true });\n\n watcher\n .watch(\n opts.path,\n queryParams,\n (phase, apiObj) => {\n const obj = apiObj as KubernetesObject;\n const objRv = (obj.metadata?.resourceVersion as string | undefined) ?? '';\n if (objRv) state.resourceVersion = objRv;\n opts.onEvent(phase as WatchPhase, obj);\n },\n (err) => {\n if (err) rejectOnce(err);\n else resolveOnce();\n },\n )\n .then((controller) => {\n controllerRef = controller;\n if (opts.signal.aborted) {\n controller.abort();\n resolveOnce();\n }\n })\n .catch((err) => rejectOnce(err));\n });\n}\n\nfunction sleep(ms: number, signal: AbortSignal): Promise<void> {\n return new Promise<void>((resolve) => {\n if (signal.aborted) {\n resolve();\n return;\n }\n const t = setTimeout(() => {\n signal.removeEventListener('abort', onAbort);\n resolve();\n }, ms);\n const onAbort = () => {\n clearTimeout(t);\n resolve();\n };\n signal.addEventListener('abort', onAbort, { once: true });\n });\n}\n","/**\n * Single-consumer async queue used by the XR watcher to expose its event\n * stream as both a callback API and a `for await` async iterable.\n *\n * Memory-bounded only by the producer. Designed for low-frequency control-plane\n * events (Kubernetes watch + status reconciliations), not high-throughput data.\n */\nexport class AsyncQueue<T> implements AsyncIterable<T> {\n private buffer: T[] = [];\n private waiters: Array<(r: IteratorResult<T>) => void> = [];\n private closed = false;\n\n push(value: T): void {\n if (this.closed) return;\n const waiter = this.waiters.shift();\n if (waiter) waiter({ value, done: false });\n else this.buffer.push(value);\n }\n\n close(): void {\n if (this.closed) return;\n this.closed = true;\n for (const w of this.waiters.splice(0)) w({ value: undefined, done: true });\n }\n\n [Symbol.asyncIterator](): AsyncIterator<T> {\n return {\n next: (): Promise<IteratorResult<T>> => {\n const v = this.buffer.shift();\n if (v !== undefined) return Promise.resolve({ value: v, done: false });\n if (this.closed) return Promise.resolve({ value: undefined, done: true });\n return new Promise<IteratorResult<T>>((resolve) => this.waiters.push(resolve));\n },\n return: (): Promise<IteratorResult<T>> => {\n this.close();\n return Promise.resolve({ value: undefined, done: true });\n },\n };\n }\n}\n","import type { KubeConfig, KubernetesObject } from '@kubernetes/client-node';\nimport { listXrCollection, listXrEvents } from '../client/lists.js';\nimport { listAndWatch } from '../client/watch.js';\nimport { AsyncQueue } from './queue.js';\nimport { buildSnapshot } from './readiness.js';\nimport type { KubernetesEvent, XrEvent, XrRef, XrSnapshot } from './types.js';\n\nexport interface CreateXrWatcherOptions {\n kubeConfig: KubeConfig;\n ref: XrRef;\n /** Disable subscribing to Kubernetes Events for the XR. Defaults to `false`. */\n disableEvents?: boolean;\n /** Aborts both the XR and Events watches and closes the iterable. */\n signal?: AbortSignal;\n}\n\nexport interface XrWatcher extends AsyncIterable<XrEvent> {\n /** Resolves with the first ready snapshot. Rejects on error or end-before-ready. */\n readonly ready: Promise<XrSnapshot>;\n /** Resolves when the watcher's background tasks have all settled. */\n readonly done: Promise<void>;\n /** Aborts the watcher and closes the iterable. Idempotent. */\n stop(): void;\n}\n\n/**\n * Subscribe to live updates of a single XR. Emits a `snapshot` on every observed\n * change, a one-shot `ready` when the XR's `Ready` condition first becomes True,\n * and (unless disabled) `k8s-event` items for Kubernetes Events targeting the XR.\n *\n * The watcher uses list-then-watch with auto-reconnect — no polling.\n */\nexport function createXrWatcher(opts: CreateXrWatcherOptions): XrWatcher {\n const queue = new AsyncQueue<XrEvent>();\n const controller = new AbortController();\n const userSignal = opts.signal;\n if (userSignal) {\n if (userSignal.aborted) controller.abort();\n else userSignal.addEventListener('abort', () => controller.abort(), { once: true });\n }\n\n let readyEmitted = false;\n let uid: string | undefined;\n let eventsStarted = false;\n let eventsTask: Promise<void> = Promise.resolve();\n const seenEventUids = new Set<string>();\n\n let resolveReady!: (snap: XrSnapshot) => void;\n let rejectReady!: (err: Error) => void;\n let readySettled = false;\n const ready = new Promise<XrSnapshot>((res, rej) => {\n resolveReady = (s) => {\n if (readySettled) return;\n readySettled = true;\n res(s);\n };\n rejectReady = (e) => {\n if (readySettled) return;\n readySettled = true;\n rej(e);\n };\n });\n // Avoid unhandled rejection if nobody awaits `ready`.\n ready.catch(() => undefined);\n\n const handleXrEvent = (phase: string, obj: KubernetesObject) => {\n if (phase === 'DELETED') {\n const err = new Error(`XR ${opts.ref.kind}/${opts.ref.name} was deleted`);\n queue.push({ type: 'error', error: err });\n rejectReady(err);\n controller.abort();\n return;\n }\n if (phase === 'BOOKMARK' || phase === 'ERROR') return;\n const snapshot = buildSnapshot(obj);\n queue.push({ type: 'snapshot', snapshot });\n if (snapshot.syncError) {\n const err = new Error(\n `XR ${opts.ref.kind}/${opts.ref.name} reconcile failed: ${snapshot.syncError.message || snapshot.syncError.reason}`,\n );\n queue.push({ type: 'error', error: err });\n rejectReady(err);\n controller.abort();\n return;\n }\n if (!readyEmitted && snapshot.ready) {\n readyEmitted = true;\n queue.push({ type: 'ready', snapshot });\n resolveReady(snapshot);\n }\n const newUid = obj.metadata?.uid as string | undefined;\n if (newUid && !uid) {\n uid = newUid;\n if (!opts.disableEvents && opts.ref.namespaced && opts.ref.namespace) {\n eventsStarted = true;\n eventsTask = startEventsWatch(opts, uid, controller.signal, queue, seenEventUids);\n }\n }\n };\n\n const xrPath = buildResourcePath(opts.ref);\n let firstListSeenItem = false;\n const xrTask = listAndWatch(opts.kubeConfig, {\n path: xrPath,\n fieldSelector: `metadata.name=${opts.ref.name}`,\n signal: controller.signal,\n list: async () => {\n const result = await listXrCollection(opts.kubeConfig, opts.ref);\n if (!firstListSeenItem) {\n if (result.items.length > 0) {\n firstListSeenItem = true;\n } else {\n const where = opts.ref.namespace ? ` in namespace ${opts.ref.namespace}` : '';\n const err = new Error(`XR ${opts.ref.kind}/${opts.ref.name} not found${where}`);\n queue.push({ type: 'error', error: err });\n rejectReady(err);\n controller.abort();\n }\n }\n return result;\n },\n onEvent: handleXrEvent,\n onError: (err) => {\n queue.push({ type: 'error', error: err });\n rejectReady(err);\n },\n });\n\n const done = (async () => {\n try {\n await xrTask;\n } finally {\n if (eventsStarted) {\n try {\n await eventsTask;\n } catch {\n /* already surfaced via onError */\n }\n }\n rejectReady(new Error('Watcher ended before XR became Ready'));\n queue.push({ type: 'end' });\n queue.close();\n }\n })();\n\n return {\n ready,\n done,\n stop: () => controller.abort(),\n [Symbol.asyncIterator]: () => queue[Symbol.asyncIterator](),\n };\n}\n\nfunction buildResourcePath(ref: XrRef): string {\n const base = ref.group ? `/apis/${ref.group}/${ref.version}` : `/api/${ref.version}`;\n if (ref.namespaced) {\n if (!ref.namespace) {\n throw new Error(`Namespace is required for namespaced resource ${ref.kind}/${ref.name}`);\n }\n return `${base}/namespaces/${ref.namespace}/${ref.plural}`;\n }\n return `${base}/${ref.plural}`;\n}\n\nasync function startEventsWatch(\n opts: CreateXrWatcherOptions,\n uid: string,\n signal: AbortSignal,\n queue: AsyncQueue<XrEvent>,\n seen: Set<string>,\n): Promise<void> {\n if (!opts.ref.namespace) return;\n const namespace = opts.ref.namespace;\n const path = `/api/v1/namespaces/${namespace}/events`;\n const fieldSelector = `involvedObject.uid=${uid}`;\n await listAndWatch(opts.kubeConfig, {\n path,\n fieldSelector,\n signal,\n list: () => listXrEvents(opts.kubeConfig, namespace, fieldSelector),\n onEvent: (phase, obj) => {\n if (phase !== 'ADDED' && phase !== 'MODIFIED') return;\n const eventUid = obj.metadata?.uid as string | undefined;\n if (eventUid && seen.has(eventUid)) return;\n if (eventUid) seen.add(eventUid);\n const event = toKubernetesEvent(obj);\n if (event) queue.push({ type: 'k8s-event', event });\n },\n onError: (err) => queue.push({ type: 'error', error: err }),\n });\n}\n\nfunction toKubernetesEvent(obj: KubernetesObject): KubernetesEvent | undefined {\n const raw = obj as KubernetesObject & {\n type?: string;\n reason?: string;\n message?: string;\n count?: number;\n firstTimestamp?: string;\n lastTimestamp?: string;\n eventTime?: string;\n involvedObject?: { kind?: string; name?: string };\n };\n if (!raw.message && !raw.reason) return undefined;\n const event: KubernetesEvent = {\n type: raw.type ?? 'Normal',\n reason: raw.reason ?? '',\n message: raw.message ?? '',\n count: raw.count ?? 1,\n };\n const first = raw.firstTimestamp ?? raw.eventTime;\n const last = raw.lastTimestamp ?? raw.eventTime ?? first;\n if (first) event.firstTimestamp = first;\n if (last) event.lastTimestamp = last;\n if (raw.involvedObject?.kind) event.involvedKind = raw.involvedObject.kind;\n if (raw.involvedObject?.name) event.involvedName = raw.involvedObject.name;\n return event;\n}\n"],"mappings":"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"}
|