@decentnetwork/lan 0.1.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.
Files changed (79) hide show
  1. package/LICENSE +31 -0
  2. package/README.md +296 -0
  3. package/bin/tun-helper-darwin-amd64 +0 -0
  4. package/bin/tun-helper-darwin-arm64 +0 -0
  5. package/bin/tun-helper-linux-amd64 +0 -0
  6. package/bin/tun-helper-linux-arm64 +0 -0
  7. package/dist/acl/acl-engine.d.ts +43 -0
  8. package/dist/acl/acl-engine.js +189 -0
  9. package/dist/acl/audit.d.ts +70 -0
  10. package/dist/acl/audit.js +144 -0
  11. package/dist/acl/index.d.ts +4 -0
  12. package/dist/acl/index.js +3 -0
  13. package/dist/acl/policy.d.ts +31 -0
  14. package/dist/acl/policy.js +102 -0
  15. package/dist/acl/types.d.ts +18 -0
  16. package/dist/acl/types.js +4 -0
  17. package/dist/carrier/frame.d.ts +18 -0
  18. package/dist/carrier/frame.js +66 -0
  19. package/dist/carrier/index.d.ts +5 -0
  20. package/dist/carrier/index.js +4 -0
  21. package/dist/carrier/packet-session.d.ts +32 -0
  22. package/dist/carrier/packet-session.js +151 -0
  23. package/dist/carrier/peer-manager.d.ts +113 -0
  24. package/dist/carrier/peer-manager.js +392 -0
  25. package/dist/carrier/types.d.ts +10 -0
  26. package/dist/carrier/types.js +11 -0
  27. package/dist/cli/commands.d.ts +223 -0
  28. package/dist/cli/commands.js +932 -0
  29. package/dist/cli/index.d.ts +7 -0
  30. package/dist/cli/index.js +196 -0
  31. package/dist/config/loader.d.ts +10 -0
  32. package/dist/config/loader.js +152 -0
  33. package/dist/daemon/index.d.ts +1 -0
  34. package/dist/daemon/index.js +1 -0
  35. package/dist/daemon/ipc.d.ts +60 -0
  36. package/dist/daemon/ipc.js +144 -0
  37. package/dist/daemon/server.d.ts +63 -0
  38. package/dist/daemon/server.js +510 -0
  39. package/dist/dns/index.d.ts +1 -0
  40. package/dist/dns/index.js +1 -0
  41. package/dist/dns/resolver.d.ts +44 -0
  42. package/dist/dns/resolver.js +82 -0
  43. package/dist/dns/server.d.ts +70 -0
  44. package/dist/dns/server.js +393 -0
  45. package/dist/dora/dora-integration.d.ts +90 -0
  46. package/dist/dora/dora-integration.js +325 -0
  47. package/dist/index.d.ts +13 -0
  48. package/dist/index.js +15 -0
  49. package/dist/ipam/index.d.ts +1 -0
  50. package/dist/ipam/index.js +1 -0
  51. package/dist/ipam/ipam.d.ts +99 -0
  52. package/dist/ipam/ipam.js +254 -0
  53. package/dist/proxy/connect-proxy.d.ts +78 -0
  54. package/dist/proxy/connect-proxy.js +204 -0
  55. package/dist/router/index.d.ts +5 -0
  56. package/dist/router/index.js +4 -0
  57. package/dist/router/ip-parser.d.ts +36 -0
  58. package/dist/router/ip-parser.js +127 -0
  59. package/dist/router/packet-router.d.ts +49 -0
  60. package/dist/router/packet-router.js +251 -0
  61. package/dist/router/session-manager.d.ts +50 -0
  62. package/dist/router/session-manager.js +138 -0
  63. package/dist/router/types.d.ts +21 -0
  64. package/dist/router/types.js +6 -0
  65. package/dist/tun/index.d.ts +3 -0
  66. package/dist/tun/index.js +2 -0
  67. package/dist/tun/route-manager.d.ts +59 -0
  68. package/dist/tun/route-manager.js +353 -0
  69. package/dist/tun/tun-device.d.ts +45 -0
  70. package/dist/tun/tun-device.js +265 -0
  71. package/dist/tun/types.d.ts +28 -0
  72. package/dist/tun/types.js +4 -0
  73. package/dist/types.d.ts +176 -0
  74. package/dist/types.js +4 -0
  75. package/dist/utils/logger.d.ts +20 -0
  76. package/dist/utils/logger.js +43 -0
  77. package/docs/CONFIGURATION.md +197 -0
  78. package/docs/INSTALL.md +145 -0
  79. package/package.json +93 -0
@@ -0,0 +1,325 @@
1
+ /**
2
+ * Dora integration — registers this node with a dora (DHCP-style) server
3
+ * and pulls the roster of known peers into the in-memory IPAM.
4
+ *
5
+ * Replaces the manual ipam.yaml dance once an operator points the config
6
+ * at a dora server's userid. The yaml file is still loaded first as a
7
+ * fallback; whatever the dora server says wins on conflict.
8
+ *
9
+ * Failure mode: if no configured dora server answers within the timeout,
10
+ * we log a warning and fall through to whatever IP/IPAM was loaded from
11
+ * disk. Decentlan is fully functional without dora — it's just that
12
+ * peers need to manually share ipam.yaml entries.
13
+ */
14
+ import { DoraClient, AllRegistriesUnavailableError } from "@decentnetwork/dora";
15
+ import { Logger } from "../utils/logger.js";
16
+ export class DoraIntegration {
17
+ opts;
18
+ client;
19
+ logger;
20
+ refreshTimer;
21
+ retryBootstrapTimer;
22
+ /** The IP dora handed us. Falls back to preferredIp on registry failure. */
23
+ allocatedIp;
24
+ /** Userids we've already attempted to friend this session — keeps the
25
+ * 60s roster refresh from spamming sendFriendRequest. */
26
+ friendRequested = new Set();
27
+ /** Set once we've successfully registered. The retry-bootstrap loop
28
+ * uses this to know when to stop trying. */
29
+ registered = false;
30
+ constructor(opts) {
31
+ this.opts = opts;
32
+ this.logger = new Logger({ prefix: "Dora" });
33
+ this.client = new DoraClient({
34
+ registryUserids: opts.config.userids ?? [],
35
+ sendText: (toUserid, text) => opts.peerManager.sendText(toUserid, text),
36
+ onText: (handler) => {
37
+ opts.peerManager.on("dora-message", (fromUserid, text) => {
38
+ handler(fromUserid, text);
39
+ });
40
+ },
41
+ timeoutMs: 10_000,
42
+ });
43
+ }
44
+ /**
45
+ * Register self with dora and pull the roster. Idempotent; safe to
46
+ * retry. Returns the IP dora allocated (or preferredIp if all servers
47
+ * were unreachable).
48
+ */
49
+ async bootstrap() {
50
+ const myUserid = this.opts.peerManager.getPubkey();
51
+ const userids = this.opts.config.userids ?? [];
52
+ if (userids.length === 0) {
53
+ this.logger.warn("dora.userids empty — skipping dora registration");
54
+ return this.opts.preferredIp ?? "";
55
+ }
56
+ // Kick session establishment with each registry so the first call
57
+ // doesn't fail on cold-start cookie/handshake delay. peer-manager
58
+ // swallows the "friend offline" error internally.
59
+ for (const id of userids) {
60
+ this.opts.peerManager
61
+ .kickSessionEstablishment(id)
62
+ .catch(() => undefined);
63
+ }
64
+ // Wait up to 30s for ANY configured dora server to come online.
65
+ // Without this, the first `register` call races the underlying
66
+ // Carrier session establishment and fails with "friend is offline
67
+ // and no express node is configured", forcing the daemon onto the
68
+ // fallback (config) IP. Use Promise.race so the first responder
69
+ // wins — slow registries don't hold up the bootstrap.
70
+ const onlineWaits = userids.map((id) => this.opts.peerManager
71
+ .waitForFriendConnected(id, 30_000)
72
+ .then((online) => ({ id, online }))
73
+ .catch(() => ({ id, online: false })));
74
+ const anyOnline = await Promise.race([
75
+ Promise.any(onlineWaits.map((p) => p.then((r) => (r.online ? r : Promise.reject(new Error("offline")))))).catch(() => null),
76
+ new Promise((r) => setTimeout(() => r(null), 30_000)),
77
+ ]);
78
+ if (!anyOnline) {
79
+ this.logger.warn("No dora server became reachable within 30s — falling back to config IP");
80
+ return this.opts.preferredIp ?? "";
81
+ }
82
+ this.logger.debug(`Dora friend online: ${anyOnline.id}`);
83
+ // Include our address so other peers fetched from list() can call
84
+ // sendFriendRequest on us. Userid alone is the bare pubkey-derived
85
+ // id; sendFriendRequest needs the address form (with nospam).
86
+ const myAddress = this.opts.peerManager.getAddress();
87
+ // Wrap the entire register-and-roster-fetch flow in ONE try/catch
88
+ // so any failure (registry unreachable, transient session blip,
89
+ // unexpected error) cleanly falls back to the config IP. An
90
+ // earlier version split this into two try blocks, with the result
91
+ // that a non-IP-collision error from the first register call
92
+ // re-threw past the AllRegistriesUnavailableError handler and
93
+ // crashed the daemon on startup.
94
+ try {
95
+ let record;
96
+ try {
97
+ record = await this.tryRegister(myUserid, myAddress, this.opts.preferredIp);
98
+ }
99
+ catch (err) {
100
+ // Common case: the preferred IP from config.yaml collides
101
+ // with another peer's reservation (every fresh `agentnet
102
+ // init` defaults to 10.86.1.10, so the second peer to
103
+ // register always loses the race). Retry once without
104
+ // requestedIp so dora picks any free slot. Errors that
105
+ // aren't IP-collision flavored fall through to the outer
106
+ // catch and trigger the fallback path.
107
+ const msg = err instanceof Error ? err.message : String(err);
108
+ const looksLikeIpCollision = /held by|in use|already taken|already in use/i.test(msg);
109
+ if (!looksLikeIpCollision)
110
+ throw err;
111
+ this.logger.warn(`Preferred IP ${this.opts.preferredIp} not available (${msg}) — requesting any free IP`);
112
+ record = await this.tryRegister(myUserid, myAddress);
113
+ }
114
+ this.allocatedIp = record.virtualIp;
115
+ this.registered = true;
116
+ this.logger.info(`Registered with dora: ${record.name} -> ${record.virtualIp}`);
117
+ // Add self to the in-memory IPAM so the local DNS server can
118
+ // answer `<my-name>.<domain>` queries from this very host.
119
+ // mergeRosterIntoIpam skips own entry (sensible — peers don't
120
+ // need to "auto-friend themselves"), but the DNS resolver
121
+ // looks at the same IPAM, so without this entry `dig snoopy.
122
+ // decent` from snoopy itself NXDOMAINs.
123
+ this.opts.ipam.assignPeer({
124
+ name: record.name,
125
+ carrierId: myUserid,
126
+ virtualIp: record.virtualIp,
127
+ services: [],
128
+ });
129
+ // Pull the full roster. We don't fail the whole bootstrap if
130
+ // list() throws — we still have our own address allocated.
131
+ try {
132
+ const roster = await this.client.list();
133
+ this.mergeRosterIntoIpam(roster, myUserid);
134
+ }
135
+ catch (err) {
136
+ this.logger.warn(`Initial roster fetch failed: ${err}`);
137
+ }
138
+ // Start periodic refresh.
139
+ const interval = this.opts.config.refreshIntervalMs ?? 60_000;
140
+ this.refreshTimer = setInterval(() => {
141
+ this.refreshRoster(myUserid).catch((err) => {
142
+ this.logger.debug(`Roster refresh: ${err}`);
143
+ });
144
+ }, interval);
145
+ return this.allocatedIp;
146
+ }
147
+ catch (err) {
148
+ if (err instanceof AllRegistriesUnavailableError) {
149
+ this.logger.warn(`All dora servers unreachable — falling back to config IP ${this.opts.preferredIp}: ${err.message}`);
150
+ }
151
+ else {
152
+ // Treat unexpected errors the same as unreachable — the
153
+ // daemon should still come up. Operator sees the warning
154
+ // and can fix.
155
+ this.logger.warn(`Dora register failed — falling back to config IP ${this.opts.preferredIp}: ${err instanceof Error ? err.message : err}`);
156
+ }
157
+ // Schedule a background retry. The TUN is already up at the
158
+ // fallback IP; we don't bring it down on success, so the
159
+ // daemon keeps running with the fallback even when register
160
+ // eventually succeeds — but at least subsequent peers
161
+ // (Ubuntu's daemon doing list()) will see our record with our
162
+ // address and can auto-friend us.
163
+ this.scheduleBootstrapRetry();
164
+ return this.opts.preferredIp ?? "";
165
+ }
166
+ }
167
+ /**
168
+ * Re-attempt the register flow every 30 seconds until it succeeds.
169
+ * Without this, a daemon that started before its dora server came
170
+ * online never makes it into the roster — peers can't auto-friend
171
+ * us, and auto-IP allocation is lost. bootstrap() re-derives its
172
+ * own myUserid so we don't need to thread it through.
173
+ */
174
+ scheduleBootstrapRetry() {
175
+ if (this.retryBootstrapTimer || this.registered)
176
+ return;
177
+ const tick = () => {
178
+ if (this.registered)
179
+ return;
180
+ this.logger.debug("Retrying dora bootstrap…");
181
+ // Don't await — let it run async and clear the timer if it
182
+ // succeeds, otherwise leave the timer running.
183
+ void this.bootstrap().then(() => {
184
+ if (this.registered && this.retryBootstrapTimer) {
185
+ clearInterval(this.retryBootstrapTimer);
186
+ this.retryBootstrapTimer = undefined;
187
+ }
188
+ });
189
+ };
190
+ this.retryBootstrapTimer = setInterval(tick, 30_000);
191
+ }
192
+ /**
193
+ * Wrap a single register call with up to 3 retries spaced 2s apart.
194
+ * The first call often races the underlying Carrier session even
195
+ * after waitForFriendConnected resolves — the SDK has a transient
196
+ * gap between the "online" event and the in-session crypto channel
197
+ * being ready for the next outgoing sendText. A short retry burst
198
+ * hides that without needing express fallback.
199
+ */
200
+ async tryRegister(myUserid, myAddress, requestedIp) {
201
+ const maxAttempts = 3;
202
+ let lastErr;
203
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
204
+ try {
205
+ return await this.client.register({
206
+ userid: myUserid,
207
+ name: this.opts.nodeName,
208
+ address: myAddress,
209
+ requestedIp,
210
+ replace: true,
211
+ });
212
+ }
213
+ catch (err) {
214
+ lastErr = err;
215
+ const msg = err instanceof Error ? err.message : String(err);
216
+ // Don't retry on definitive rejections (name/IP collision):
217
+ // those are protocol-level errors that won't fix themselves.
218
+ if (/held by|in use|already taken|already in use|out of range|already registered/i.test(msg)) {
219
+ throw err;
220
+ }
221
+ if (attempt < maxAttempts) {
222
+ this.logger.debug(`register attempt ${attempt}/${maxAttempts} failed: ${msg} — retrying in 2s`);
223
+ await new Promise((r) => setTimeout(r, 2000));
224
+ }
225
+ }
226
+ }
227
+ throw lastErr;
228
+ }
229
+ stop() {
230
+ if (this.refreshTimer) {
231
+ clearInterval(this.refreshTimer);
232
+ this.refreshTimer = undefined;
233
+ }
234
+ if (this.retryBootstrapTimer) {
235
+ clearInterval(this.retryBootstrapTimer);
236
+ this.retryBootstrapTimer = undefined;
237
+ }
238
+ }
239
+ getAllocatedIp() {
240
+ return this.allocatedIp;
241
+ }
242
+ async refreshRoster(myUserid) {
243
+ const roster = await this.client.list();
244
+ this.mergeRosterIntoIpam(roster, myUserid);
245
+ }
246
+ /**
247
+ * Stamp dora records into the in-memory IPAM. We DON'T persist to
248
+ * ipam.yaml — that file remains operator-managed. Dora is treated as
249
+ * a live overlay that's refreshed on every restart.
250
+ *
251
+ * Conflict policy: dora wins. If ipam.yaml had a peer with a stale IP,
252
+ * the dora record overwrites it (assignPeer dedupes by name + carrierId).
253
+ */
254
+ mergeRosterIntoIpam(roster, myUserid) {
255
+ let added = 0;
256
+ let updated = 0;
257
+ for (const entry of roster) {
258
+ if (entry.userid === myUserid)
259
+ continue; // skip self
260
+ const existing = this.opts.ipam.resolveCarrierId(entry.userid);
261
+ if (!existing) {
262
+ added += 1;
263
+ }
264
+ else if (existing.virtualIp !== entry.virtualIp ||
265
+ existing.name !== entry.name) {
266
+ updated += 1;
267
+ }
268
+ else {
269
+ // Entry unchanged in IPAM, but still attempt friending — a
270
+ // roster refresh may pick up a peer that registered earlier
271
+ // and is already in our IPAM (because we previously fetched
272
+ // them) but never got friended.
273
+ this.maybeFriend(entry);
274
+ continue;
275
+ }
276
+ this.opts.ipam.assignPeer({
277
+ name: entry.name,
278
+ carrierId: entry.userid,
279
+ virtualIp: entry.virtualIp,
280
+ services: existing?.services ?? [],
281
+ });
282
+ this.maybeFriend(entry);
283
+ }
284
+ if (added > 0 || updated > 0) {
285
+ this.logger.info(`Roster refreshed: ${added} new, ${updated} updated, ${roster.length} total`);
286
+ }
287
+ }
288
+ /**
289
+ * Send an outbound friend request to a roster entry if we haven't
290
+ * already (and aren't already friends). Idempotent at multiple
291
+ * levels: in-process guard via `friendRequested`, SDK-level
292
+ * short-circuit on `acceptedAt`, and recipient-side auto-accept.
293
+ *
294
+ * Address is optional in the protocol for backward-compat with
295
+ * older dora records; without it, we can't initiate the request
296
+ * (the SDK needs the nospam token embedded in the address). The
297
+ * caller logs a one-time warning so the operator knows to either
298
+ * re-register the peer or fall back to a manual friend-request.
299
+ */
300
+ maybeFriend(entry) {
301
+ if (this.friendRequested.has(entry.userid))
302
+ return;
303
+ if (this.opts.peerManager.isFriend(entry.userid)) {
304
+ this.friendRequested.add(entry.userid);
305
+ return;
306
+ }
307
+ if (!entry.address) {
308
+ this.logger.warn(`Roster entry ${entry.name} (${entry.userid.slice(0, 12)}...) has no address — can't auto-friend. ` +
309
+ `Have them re-register against a newer dora server, or run 'agentnet friend-request' manually.`);
310
+ this.friendRequested.add(entry.userid); // don't keep warning every 60s
311
+ return;
312
+ }
313
+ this.friendRequested.add(entry.userid);
314
+ this.opts.peerManager
315
+ .sendFriendRequest(entry.address, `dora roster auto-friend (${this.opts.nodeName})`)
316
+ .then(() => {
317
+ this.logger.info(`Auto-friend sent to ${entry.name} (${entry.userid.slice(0, 12)}...)`);
318
+ })
319
+ .catch((err) => {
320
+ this.logger.warn(`Auto-friend ${entry.name}: ${err instanceof Error ? err.message : err}`);
321
+ // Allow another attempt on the next refresh cycle.
322
+ this.friendRequested.delete(entry.userid);
323
+ });
324
+ }
325
+ }
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Decent AgentNet — Main entry point
4
+ * Dispatches to CLI handler.
5
+ *
6
+ * NOTE: the EventEmitter.defaultMaxListeners adjustment MUST happen before
7
+ * the SDK is imported. The Carrier SDK's UdpTransport attaches one listener
8
+ * per bootstrap node and per relay, easily exceeding Node's default cap of
9
+ * 10. If we raise the cap after the SDK loads, EventEmitter instances
10
+ * created during the SDK's module initialization still use the old default
11
+ * and produce a wall of MaxListenersExceededWarning lines at runtime.
12
+ */
13
+ import "./cli/index.js";
package/dist/index.js ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Decent AgentNet — Main entry point
4
+ * Dispatches to CLI handler.
5
+ *
6
+ * NOTE: the EventEmitter.defaultMaxListeners adjustment MUST happen before
7
+ * the SDK is imported. The Carrier SDK's UdpTransport attaches one listener
8
+ * per bootstrap node and per relay, easily exceeding Node's default cap of
9
+ * 10. If we raise the cap after the SDK loads, EventEmitter instances
10
+ * created during the SDK's module initialization still use the old default
11
+ * and produce a wall of MaxListenersExceededWarning lines at runtime.
12
+ */
13
+ import { EventEmitter } from "events";
14
+ EventEmitter.defaultMaxListeners = 100;
15
+ import "./cli/index.js";
@@ -0,0 +1 @@
1
+ export { Ipam, type IpamConfig } from "./ipam.js";
@@ -0,0 +1 @@
1
+ export { Ipam } from "./ipam.js";
@@ -0,0 +1,99 @@
1
+ /**
2
+ * IPAM - IP Address Management
3
+ * Maps Carrier IDs to virtual IPs, hostnames, and services
4
+ */
5
+ import type { IpamRecord, Service } from "../types.js";
6
+ export interface IpamConfig {
7
+ namespace: string;
8
+ peers: IpamRecord[];
9
+ }
10
+ export declare class Ipam {
11
+ private config;
12
+ private filePath;
13
+ private logger;
14
+ private ipCache;
15
+ private idCache;
16
+ private nameCache;
17
+ constructor(config: IpamConfig, filePath: string);
18
+ static load(filePath: string): Promise<Ipam>;
19
+ static loadOrCreate(filePath: string, namespace?: string): Promise<Ipam>;
20
+ /**
21
+ * Resolve hostname to virtual IP
22
+ */
23
+ resolveName(name: string): string | null;
24
+ /**
25
+ * Resolve virtual IP to IpamRecord
26
+ */
27
+ resolveIp(ip: string): IpamRecord | null;
28
+ /**
29
+ * Resolve Carrier ID to IpamRecord
30
+ */
31
+ resolveCarrierId(carrierId: string): IpamRecord | null;
32
+ /**
33
+ * Get all peers
34
+ */
35
+ getPeers(): IpamRecord[];
36
+ /**
37
+ * Add or update a peer
38
+ */
39
+ assignPeer(record: IpamRecord): void;
40
+ /**
41
+ * Remove a peer by name or Carrier ID
42
+ */
43
+ removePeer(identifier: string): boolean;
44
+ /**
45
+ * Check if IP is allocated
46
+ */
47
+ isIpAllocated(ip: string): boolean;
48
+ /**
49
+ * Find next available IP in subnet
50
+ * Assumes subnet like "10.86.0.0/16"
51
+ */
52
+ findNextAvailableIp(subnet: string, start?: string): string;
53
+ /**
54
+ * Get service port for peer
55
+ */
56
+ getServicePort(peerName: string, serviceName: string): number | null;
57
+ /**
58
+ * Get all services for peer
59
+ */
60
+ getServices(peerName: string): Service[];
61
+ /**
62
+ * Save IPAM config to file
63
+ */
64
+ save(): Promise<void>;
65
+ /**
66
+ * Check if peer is expired
67
+ */
68
+ isExpired(peerName: string): boolean;
69
+ /**
70
+ * Deterministically derive a virtual IP from a Carrier userid, in the
71
+ * same style as ARP/DHCP-by-MAC: every peer computes the same IP for the
72
+ * same userid, so the network is consistent without manual coordination.
73
+ *
74
+ * Hash(userid)[0..1] → last two octets of 10.86.X.Y. Avoids x.0 and x.255.
75
+ * Returns the IP regardless of whether it's already in the IPAM.
76
+ */
77
+ static deterministicIpForUserid(userid: string): string;
78
+ /**
79
+ * Ensure each friend has an IPAM record. Returns the number of new
80
+ * records added. Existing entries (manual or previously auto-assigned)
81
+ * are preserved.
82
+ *
83
+ * On collision (different userid hashes to an already-taken IP), walks
84
+ * the 4th octet until a free slot is found.
85
+ */
86
+ autoAssignFriends(friends: {
87
+ pubkey: string;
88
+ name?: string;
89
+ }[], selfUserid?: string): number;
90
+ /**
91
+ * Get config
92
+ */
93
+ getConfig(): IpamConfig;
94
+ /**
95
+ * Get namespace
96
+ */
97
+ getNamespace(): string;
98
+ private rebuildCache;
99
+ }