@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.
@@ -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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"}