@spine-event-engine/deployment-gke 2.0.0-snapshot.11

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,44 @@
1
+ /*
2
+ * Copyright 2026, CodeMatters. All rights reserved.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5
+ * in compliance with the License. You may obtain a copy of the License at
6
+ *
7
+ * https://www.apache.org/licenses/LICENSE-2.0
8
+ *
9
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
10
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11
+ * or implied. See the License for the specific language governing permissions and limitations under
12
+ * the License.
13
+ */
14
+ /**
15
+ * Records a contained GKE discovery warning without changing discovery outcomes.
16
+ *
17
+ * @param logger Receives the contained record when supplied.
18
+ */
19
+ export const gkeDiscoveryLog = Object.freeze({
20
+ warn(logger) {
21
+ if (logger === undefined)
22
+ return;
23
+ try {
24
+ const record = logger.withMetadata({
25
+ operation: "deployment.gke.discovery.refresh",
26
+ reasonCode: "failed",
27
+ });
28
+ const emit = record.warn.bind(record);
29
+ const emitted = emit("deployment.gke.discovery.refresh_failed");
30
+ if (gkeDiscoveryLog.isPromiseLike(emitted))
31
+ void Promise.resolve(emitted).catch(() => undefined);
32
+ }
33
+ catch {
34
+ // Logging must not affect the containing runtime outcome.
35
+ }
36
+ },
37
+ isPromiseLike(value) {
38
+ return ((typeof value === "object" || typeof value === "function") &&
39
+ value !== null &&
40
+ "then" in value &&
41
+ typeof value.then === "function");
42
+ },
43
+ });
44
+ //# sourceMappingURL=gke-log.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gke-log.js","sourceRoot":"","sources":["../../src/discovery/gke-log.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAIH;;;;GAIG;AACH,MAAM,CAAC,MAAM,eAAe,GAGvB,MAAM,CAAC,MAAM,CAAC;IACjB,IAAI,CAAC,MAA6B;QAChC,IAAI,MAAM,KAAK,SAAS;YAAE,OAAO;QACjC,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,CAAC,YAAY,CAAC;gBACjC,SAAS,EAAE,kCAAkC;gBAC7C,UAAU,EAAE,QAAQ;aACrB,CAAC,CAAC;YACH,MAAM,IAAI,GAA+B,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YAClE,MAAM,OAAO,GAAG,IAAI,CAAC,yCAAyC,CAAC,CAAC;YAChE,IAAI,eAAe,CAAC,aAAa,CAAC,OAAO,CAAC;gBACxC,KAAK,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QACzD,CAAC;QAAC,MAAM,CAAC;YACP,0DAA0D;QAC5D,CAAC;IACH,CAAC;IAED,aAAa,CAAC,KAAc;QAC1B,OAAO,CACL,CAAC,OAAO,KAAK,KAAK,QAAQ,IAAI,OAAO,KAAK,KAAK,UAAU,CAAC;YAC1D,KAAK,KAAK,IAAI;YACd,MAAM,IAAI,KAAK;YACf,OAAQ,KAA4B,CAAC,IAAI,KAAK,UAAU,CACzD,CAAC;IACJ,CAAC;CACF,CAAC,CAAC"}
@@ -0,0 +1,104 @@
1
+ import { ApplicationNode, type NodeDiscovery, type NodeScheduler } from "@spine-event-engine/deployment";
2
+ import type { ILogLayer } from "loglayer";
3
+ /**
4
+ * Represents one DNS address with its optional answer TTL in seconds.
5
+ */
6
+ export interface GkeDnsAddress {
7
+ /**
8
+ *
9
+ * Provides one ready-Pod IPv4 or IPv6 address.
10
+ */
11
+ readonly address: string;
12
+ /**
13
+ *
14
+ * Provides the record lifetime in seconds when DNS includes it.
15
+ */
16
+ readonly ttl?: number;
17
+ }
18
+ /**
19
+ * Resolves the complete ready-Pod address set for one headless Service.
20
+ */
21
+ export interface GkeDnsResolver {
22
+ /**
23
+ * Resolves the current answer and cooperates with cancellation.
24
+ *
25
+ * @param serviceName Supplies the configured headless-Service DNS name.
26
+ * @param signal Cancels the pending DNS request.
27
+ * @returns The complete address answer or an empty answer for name-not-found.
28
+ */
29
+ resolve(serviceName: string, signal: AbortSignal): Promise<readonly GkeDnsAddress[]>;
30
+ }
31
+ /**
32
+ * Configures GKE headless-Service discovery for one standalone Gateway.
33
+ */
34
+ export interface GkeNodeDiscoveryOptions {
35
+ /**
36
+ *
37
+ * Identifies the headless-Service DNS name.
38
+ */
39
+ readonly serviceName: string;
40
+ /**
41
+ *
42
+ * Identifies the reachable application TCP port.
43
+ */
44
+ readonly port: number;
45
+ /**
46
+ *
47
+ * Selects the HTTP transport scheme.
48
+ */
49
+ readonly scheme?: "http" | "https";
50
+ /**
51
+ *
52
+ * Sets the maximum interval between DNS lookups in milliseconds.
53
+ */
54
+ readonly refreshIntervalMs?: number;
55
+ /**
56
+ *
57
+ * Supplies the DNS lookup adapter.
58
+ */
59
+ readonly resolver?: GkeDnsResolver;
60
+ /**
61
+ *
62
+ * Schedules future DNS lookups.
63
+ */
64
+ readonly scheduler?: NodeScheduler;
65
+ /**
66
+ *
67
+ * Returns the current time for TTL validity.
68
+ *
69
+ * @returns The current Unix time in milliseconds.
70
+ */
71
+ readonly now?: () => number;
72
+ /**
73
+ * Application-owned logger reserved for component-local records. The component
74
+ * does not retain or close the supplied logger.
75
+ */
76
+ readonly logger?: ILogLayer;
77
+ }
78
+ /**
79
+ * Publishes ready GKE Pods from a headless-Service DNS answer.
80
+ */
81
+ export declare class GkeNodeDiscovery implements NodeDiscovery {
82
+ #private;
83
+ /**
84
+ * Creates discovery with a ten-second refresh interval by default.
85
+ *
86
+ * @param options Supplies the Service name, reachable port, and optional runtime seams.
87
+ */
88
+ constructor(options: GkeNodeDiscoveryOptions);
89
+ /**
90
+ * Starts the only complete membership watch.
91
+ *
92
+ * @param onSnapshot Receives every successful or expired membership snapshot.
93
+ * @returns Stops future refreshes and joins the active DNS request.
94
+ */
95
+ watch(onSnapshot: (nodes: readonly ApplicationNode[]) => void): () => Promise<void>;
96
+ /**
97
+ * Cancels scheduled and in-flight DNS work permanently.
98
+ *
99
+ * @returns Completes after the admitted resolver request settles.
100
+ */
101
+ close(): Promise<void>;
102
+ }
103
+ export { NodeDnsResolver, type DnsLookup } from "./node-dns-resolver.js";
104
+ //# sourceMappingURL=gke-node-discovery.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gke-node-discovery.d.ts","sourceRoot":"","sources":["../../src/discovery/gke-node-discovery.ts"],"names":[],"mappings":"AAcA,OAAO,EACL,eAAe,EACf,KAAK,aAAa,EAClB,KAAK,aAAa,EACnB,MAAM,gCAAgC,CAAC;AACxC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAK1C;;GAEG;AACH,MAAM,WAAW,aAAa;IAG5B;;;OAGG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IAEzB;;;OAGG;IACH,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAG7B;;;;;;OAMG;IACH,OAAO,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,SAAS,aAAa,EAAE,CAAC,CAAC;CACtF;AAED;;GAEG;AACH,MAAM,WAAW,uBAAuB;IAGtC;;;OAGG;IACH,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAE7B;;;OAGG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAEtB;;;OAGG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAEnC;;;OAGG;IACH,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAEpC;;;OAGG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,cAAc,CAAC;IAEnC;;;OAGG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,aAAa,CAAC;IAEnC;;;;;OAKG;IACH,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IAE5B;;;OAGG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,CAAC;CAC7B;AAcD;;GAEG;AACH,qBAAa,gBAAiB,YAAW,aAAa;;IAqBpD;;;;OAIG;gBACS,OAAO,EAAE,uBAAuB;IAmB5C;;;;;OAKG;IACH,KAAK,CAAC,UAAU,EAAE,CAAC,KAAK,EAAE,SAAS,eAAe,EAAE,KAAK,IAAI,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC;IASnF;;;;OAIG;IACH,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CA0GvB;AAED,OAAO,EAAE,eAAe,EAAE,KAAK,SAAS,EAAE,MAAM,wBAAwB,CAAC"}
@@ -0,0 +1,198 @@
1
+ /*
2
+ * Copyright 2026, CodeMatters. All rights reserved.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5
+ * in compliance with the License. You may obtain a copy of the License at
6
+ *
7
+ * https://www.apache.org/licenses/LICENSE-2.0
8
+ *
9
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
10
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11
+ * or implied. See the License for the specific language governing permissions and limitations under
12
+ * the License.
13
+ */
14
+ import { ApplicationNode, } from "@spine-event-engine/deployment";
15
+ import { NodeDnsResolver } from "./node-dns-resolver.js";
16
+ import { gkeDiscoveryLog } from "./gke-log.js";
17
+ const systemScheduler = {
18
+ schedule(delayMs, onTick) {
19
+ const timer = setTimeout(onTick, delayMs);
20
+ timer.unref();
21
+ return () => {
22
+ clearTimeout(timer);
23
+ };
24
+ },
25
+ };
26
+ const maximumRefreshes = 2;
27
+ /**
28
+ * Publishes ready GKE Pods from a headless-Service DNS answer.
29
+ */
30
+ export class GkeNodeDiscovery {
31
+ #serviceName;
32
+ #port;
33
+ #scheme;
34
+ #refreshIntervalMs;
35
+ #resolver;
36
+ #scheduler;
37
+ #now;
38
+ #logger;
39
+ #cancel;
40
+ #expiryCancel;
41
+ #controllers = new Set();
42
+ #watcher;
43
+ #refreshes = new Set();
44
+ #validUntilMs;
45
+ #expired = false;
46
+ #empty = false;
47
+ #closed = false;
48
+ #closing;
49
+ #epoch = 0;
50
+ /**
51
+ * Creates discovery with a ten-second refresh interval by default.
52
+ *
53
+ * @param options Supplies the Service name, reachable port, and optional runtime seams.
54
+ */
55
+ constructor(options) {
56
+ if (!ApplicationNode.tls(options.serviceName))
57
+ throw new Error("GKE Service name is invalid.");
58
+ if (!Number.isSafeInteger(options.port) || options.port < 1 || options.port > 65_535)
59
+ throw new RangeError("GKE node port must be a valid TCP port.");
60
+ if (options.refreshIntervalMs !== undefined &&
61
+ (!Number.isSafeInteger(options.refreshIntervalMs) || options.refreshIntervalMs < 1))
62
+ throw new RangeError("GKE refresh interval must be a positive safe integer.");
63
+ this.#serviceName = ApplicationNode.tls(options.serviceName);
64
+ this.#port = options.port;
65
+ this.#scheme = options.scheme ?? "http";
66
+ this.#refreshIntervalMs = options.refreshIntervalMs ?? 10_000;
67
+ this.#resolver = options.resolver ?? new NodeDnsResolver();
68
+ this.#scheduler = options.scheduler ?? systemScheduler;
69
+ this.#now = options.now ?? Date.now;
70
+ this.#logger = options.logger;
71
+ }
72
+ /**
73
+ * Starts the only complete membership watch.
74
+ *
75
+ * @param onSnapshot Receives every successful or expired membership snapshot.
76
+ * @returns Stops future refreshes and joins the active DNS request.
77
+ */
78
+ watch(onSnapshot) {
79
+ if (this.#closed)
80
+ throw new Error("GKE node discovery is closed.");
81
+ if (this.#watcher !== undefined)
82
+ throw new Error("GKE node discovery supports one active watch.");
83
+ this.#watcher = onSnapshot;
84
+ this.#schedule(0);
85
+ return () => this.close();
86
+ }
87
+ /**
88
+ * Cancels scheduled and in-flight DNS work permanently.
89
+ *
90
+ * @returns Completes after the admitted resolver request settles.
91
+ */
92
+ close() {
93
+ if (this.#closing !== undefined)
94
+ return this.#closing;
95
+ this.#closing = this.#close();
96
+ return this.#closing;
97
+ }
98
+ async #close() {
99
+ this.#closed = true;
100
+ this.#cancel?.();
101
+ this.#expiryCancel?.();
102
+ for (const controller of this.#controllers)
103
+ controller.abort();
104
+ this.#watcher = undefined;
105
+ await Promise.allSettled([...this.#refreshes]);
106
+ }
107
+ #schedule(delayMs) {
108
+ this.#cancel?.();
109
+ this.#cancel = this.#scheduler.schedule(delayMs, () => {
110
+ this.#cancel = undefined;
111
+ if (this.#expired)
112
+ this.#schedule(this.#refreshIntervalMs);
113
+ if (this.#controllers.size >= maximumRefreshes)
114
+ return;
115
+ const refresh = this.#refresh(++this.#epoch);
116
+ this.#refreshes.add(refresh);
117
+ // spine-log-boundary: deployment_gke.discovery_refresh_tail
118
+ void refresh.catch(() => undefined).finally(() => this.#refreshes.delete(refresh));
119
+ });
120
+ }
121
+ #scheduleExpiry(delayMs) {
122
+ this.#expiryCancel?.();
123
+ this.#expiryCancel = this.#scheduler.schedule(delayMs, () => {
124
+ if (!this.#closed &&
125
+ this.#validUntilMs !== undefined &&
126
+ this.#now() >= this.#validUntilMs &&
127
+ !this.#empty) {
128
+ this.#watcher?.([]);
129
+ this.#expired = true;
130
+ this.#empty = true;
131
+ this.#schedule(this.#refreshIntervalMs);
132
+ }
133
+ });
134
+ }
135
+ async #refresh(epoch) {
136
+ if (this.#closed)
137
+ return;
138
+ const controller = new AbortController();
139
+ this.#controllers.add(controller);
140
+ try {
141
+ const answer = await this.#resolver.resolve(this.#serviceName, controller.signal);
142
+ if (controller.signal.aborted || epoch !== this.#epoch)
143
+ return;
144
+ const nodes = this.#nodes(answer);
145
+ this.#watcher?.(nodes);
146
+ this.#expired = false;
147
+ this.#empty = nodes.length === 0;
148
+ this.#expiryCancel?.();
149
+ this.#expiryCancel = undefined;
150
+ const ttlMs = this.#ttlMs(answer);
151
+ this.#validUntilMs = this.#now() + ttlMs;
152
+ this.#schedule(nodes.length === 0 ? this.#refreshIntervalMs : Math.min(this.#refreshIntervalMs, ttlMs));
153
+ if (nodes.length > 0)
154
+ this.#scheduleExpiry(ttlMs);
155
+ }
156
+ catch {
157
+ if (controller.signal.aborted || epoch !== this.#epoch)
158
+ return;
159
+ // spine-log-boundary: deployment_gke.discovery_refresh
160
+ gkeDiscoveryLog.warn(this.#logger);
161
+ if (this.#validUntilMs !== undefined &&
162
+ this.#now() >= this.#validUntilMs &&
163
+ !this.#expired &&
164
+ !this.#empty) {
165
+ this.#watcher?.([]);
166
+ this.#expired = true;
167
+ this.#empty = true;
168
+ }
169
+ this.#schedule(this.#refreshIntervalMs);
170
+ }
171
+ finally {
172
+ this.#controllers.delete(controller);
173
+ }
174
+ }
175
+ #ttlMs(answer) {
176
+ const positive = answer
177
+ .map((entry) => entry.ttl)
178
+ .filter((ttl) => ttl !== undefined && Number.isFinite(ttl) && ttl > 0);
179
+ return positive.length === 0 ? this.#refreshIntervalMs : Math.min(...positive) * 1000;
180
+ }
181
+ #nodes(answer) {
182
+ const nodes = new Map();
183
+ for (const entry of answer) {
184
+ const host = entry.address.includes(":") ? `[${entry.address}]` : entry.address;
185
+ const endpoint = `${this.#scheme}://${host}:${this.#port.toString()}`;
186
+ const canonical = new ApplicationNode({ id: "gke", endpoint }).endpoint;
187
+ const node = new ApplicationNode({
188
+ id: `gke/${canonical}/${this.#scheme === "https" ? this.#serviceName : ""}`,
189
+ endpoint: canonical,
190
+ ...(this.#scheme === "https" ? { tlsServerName: this.#serviceName } : {}),
191
+ });
192
+ nodes.set(node.id, node);
193
+ }
194
+ return [...nodes.values()];
195
+ }
196
+ }
197
+ export { NodeDnsResolver } from "./node-dns-resolver.js";
198
+ //# sourceMappingURL=gke-node-discovery.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gke-node-discovery.js","sourceRoot":"","sources":["../../src/discovery/gke-node-discovery.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,58 @@
1
+ import type { GkeDnsAddress, GkeDnsResolver } from "./gke-node-discovery.js";
2
+ /**
3
+ * Supplies cancellable TTL-aware Node DNS address-family lookups.
4
+ */
5
+ export interface DnsLookup {
6
+ /**
7
+ * Resolves TTL-aware IPv4 records.
8
+ *
9
+ * @param name Supplies the DNS name.
10
+ * @param options Requests TTL metadata.
11
+ * @returns The current IPv4 records.
12
+ */
13
+ resolve4(name: string, options: {
14
+ readonly ttl: true;
15
+ }): Promise<readonly {
16
+ readonly address: string;
17
+ readonly ttl: number;
18
+ }[]>;
19
+ /**
20
+ * Resolves TTL-aware IPv6 records.
21
+ *
22
+ * @param name Supplies the DNS name.
23
+ * @param options Requests TTL metadata.
24
+ * @returns The current IPv6 records.
25
+ */
26
+ resolve6(name: string, options: {
27
+ readonly ttl: true;
28
+ }): Promise<readonly {
29
+ readonly address: string;
30
+ readonly ttl: number;
31
+ }[]>;
32
+ /**
33
+ * Cancels every active lookup.
34
+ */
35
+ cancel(): void;
36
+ }
37
+ /**
38
+ * Resolves IPv4 and IPv6 headless-Service records through Node DNS.
39
+ */
40
+ export declare class NodeDnsResolver implements GkeDnsResolver {
41
+ #private;
42
+ /**
43
+ * Creates the Node DNS adapter.
44
+ *
45
+ * @param create Supplies a resolver factory for deterministic integration tests.
46
+ */
47
+ constructor(create?: () => DnsLookup);
48
+ /**
49
+ * Resolves both address families and exposes DNS TTLs when Node supplies them.
50
+ *
51
+ * @param serviceName Supplies the headless-Service DNS name.
52
+ * @param signal Cancels the resolver request.
53
+ * @returns All successful A and AAAA records, or an empty name-not-found answer.
54
+ */
55
+ resolve(serviceName: string, signal: AbortSignal): Promise<readonly GkeDnsAddress[]>;
56
+ private static empty;
57
+ }
58
+ //# sourceMappingURL=node-dns-resolver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node-dns-resolver.d.ts","sourceRoot":"","sources":["../../src/discovery/node-dns-resolver.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAE7E;;GAEG;AACH,MAAM,WAAW,SAAS;IAGxB;;;;;;OAMG;IACH,QAAQ,CACN,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE;QAAE,QAAQ,CAAC,GAAG,EAAE,IAAI,CAAA;KAAE,GAC9B,OAAO,CAAC,SAAS;QAAE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC,CAAC;IAE1E;;;;;;OAMG;IACH,QAAQ,CACN,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE;QAAE,QAAQ,CAAC,GAAG,EAAE,IAAI,CAAA;KAAE,GAC9B,OAAO,CAAC,SAAS;QAAE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC,CAAC;IAE1E;;OAEG;IACH,MAAM,IAAI,IAAI,CAAC;CAChB;AAED;;GAEG;AACH,qBAAa,eAAgB,YAAW,cAAc;;IAGpD;;;;OAIG;gBACS,MAAM,GAAE,MAAM,SAAgC;IAI1D;;;;;;OAMG;IACG,OAAO,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,SAAS,aAAa,EAAE,CAAC;IAkC1F,OAAO,CAAC,MAAM,CAAC,KAAK;CASrB"}
@@ -0,0 +1,78 @@
1
+ /*
2
+ * Copyright 2026, CodeMatters. All rights reserved.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5
+ * in compliance with the License. You may obtain a copy of the License at
6
+ *
7
+ * https://www.apache.org/licenses/LICENSE-2.0
8
+ *
9
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
10
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11
+ * or implied. See the License for the specific language governing permissions and limitations under
12
+ * the License.
13
+ */
14
+ import { Resolver } from "node:dns/promises";
15
+ /**
16
+ * Resolves IPv4 and IPv6 headless-Service records through Node DNS.
17
+ */
18
+ export class NodeDnsResolver {
19
+ #create;
20
+ /**
21
+ * Creates the Node DNS adapter.
22
+ *
23
+ * @param create Supplies a resolver factory for deterministic integration tests.
24
+ */
25
+ constructor(create = () => new Resolver()) {
26
+ this.#create = create;
27
+ }
28
+ /**
29
+ * Resolves both address families and exposes DNS TTLs when Node supplies them.
30
+ *
31
+ * @param serviceName Supplies the headless-Service DNS name.
32
+ * @param signal Cancels the resolver request.
33
+ * @returns All successful A and AAAA records, or an empty name-not-found answer.
34
+ */
35
+ async resolve(serviceName, signal) {
36
+ const resolver = this.#create();
37
+ if (signal.aborted) {
38
+ resolver.cancel();
39
+ signal.throwIfAborted();
40
+ }
41
+ const cancel = () => {
42
+ resolver.cancel();
43
+ };
44
+ signal.addEventListener("abort", cancel, { once: true });
45
+ try {
46
+ const lookup = async (request) => {
47
+ try {
48
+ return await request;
49
+ }
50
+ catch (error) {
51
+ if (!NodeDnsResolver.empty(error))
52
+ resolver.cancel();
53
+ if (NodeDnsResolver.empty(error))
54
+ return [];
55
+ throw error;
56
+ }
57
+ };
58
+ const [ipv4, ipv6] = await Promise.all([
59
+ lookup(resolver.resolve4(serviceName, { ttl: true })),
60
+ lookup(resolver.resolve6(serviceName, { ttl: true })),
61
+ ]);
62
+ signal.throwIfAborted();
63
+ const records = [...ipv4, ...ipv6];
64
+ return records.map((record) => ({ address: record.address, ttl: record.ttl }));
65
+ }
66
+ finally {
67
+ signal.removeEventListener("abort", cancel);
68
+ }
69
+ }
70
+ static empty(error) {
71
+ return (typeof error === "object" &&
72
+ error !== null &&
73
+ "code" in error &&
74
+ (error.code === "ENOTFOUND" ||
75
+ error.code === "ENODATA"));
76
+ }
77
+ }
78
+ //# sourceMappingURL=node-dns-resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node-dns-resolver.js","sourceRoot":"","sources":["../../src/discovery/node-dns-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAwC7C;;GAEG;AACH,MAAM,OAAO,eAAe;IACjB,OAAO,CAAkB;IAElC;;;;OAIG;IACH,YAAY,SAA0B,GAAG,EAAE,CAAC,IAAI,QAAQ,EAAE;QACxD,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC;IACxB,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,OAAO,CAAC,WAAmB,EAAE,MAAmB;QACpD,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;QAChC,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;YACnB,QAAQ,CAAC,MAAM,EAAE,CAAC;YAClB,MAAM,CAAC,cAAc,EAAE,CAAC;QAC1B,CAAC;QACD,MAAM,MAAM,GAAG,GAAG,EAAE;YAClB,QAAQ,CAAC,MAAM,EAAE,CAAC;QACpB,CAAC,CAAC;QACF,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QACzD,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,KAAK,EAClB,OAA+E,EAC/E,EAAE;gBACF,IAAI,CAAC;oBACH,OAAO,MAAM,OAAO,CAAC;gBACvB,CAAC;gBAAC,OAAO,KAAK,EAAE,CAAC;oBACf,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC,KAAK,CAAC;wBAAE,QAAQ,CAAC,MAAM,EAAE,CAAC;oBACrD,IAAI,eAAe,CAAC,KAAK,CAAC,KAAK,CAAC;wBAAE,OAAO,EAAE,CAAC;oBAC5C,MAAM,KAAK,CAAC;gBACd,CAAC;YACH,CAAC,CAAC;YACF,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;gBACrC,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,WAAW,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC;gBACrD,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,WAAW,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC;aACtD,CAAC,CAAC;YACH,MAAM,CAAC,cAAc,EAAE,CAAC;YACxB,MAAM,OAAO,GAAG,CAAC,GAAG,IAAI,EAAE,GAAG,IAAI,CAAC,CAAC;YACnC,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QACjF,CAAC;gBAAS,CAAC;YACT,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAC9C,CAAC;IACH,CAAC;IAEO,MAAM,CAAC,KAAK,CAAC,KAAc;QACjC,OAAO,CACL,OAAO,KAAK,KAAK,QAAQ;YACzB,KAAK,KAAK,IAAI;YACd,MAAM,IAAI,KAAK;YACf,CAAE,KAAqC,CAAC,IAAI,KAAK,WAAW;gBACzD,KAAqC,CAAC,IAAI,KAAK,SAAS,CAAC,CAC7D,CAAC;IACJ,CAAC;CACF"}
@@ -0,0 +1,2 @@
1
+ export { GkeNodeDiscovery, NodeDnsResolver, type DnsLookup, type GkeDnsAddress, type GkeDnsResolver, type GkeNodeDiscoveryOptions, } from "./discovery/gke-node-discovery.js";
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAcA,OAAO,EACL,gBAAgB,EAChB,eAAe,EACf,KAAK,SAAS,EACd,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,uBAAuB,GAC7B,MAAM,mCAAmC,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,15 @@
1
+ /*
2
+ * Copyright 2026, CodeMatters. All rights reserved.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5
+ * in compliance with the License. You may obtain a copy of the License at
6
+ *
7
+ * https://www.apache.org/licenses/LICENSE-2.0
8
+ *
9
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
10
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11
+ * or implied. See the License for the specific language governing permissions and limitations under
12
+ * the License.
13
+ */
14
+ export { GkeNodeDiscovery, NodeDnsResolver, } from "./discovery/gke-node-discovery.js";
15
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EACL,gBAAgB,EAChB,eAAe,GAKhB,MAAM,mCAAmC,CAAC"}