@hasna/microservices 0.0.6 → 0.0.8

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.
@@ -1,60 +1,38 @@
1
1
  /**
2
2
  * Namecheap API integration for domain management
3
3
  *
4
+ * Uses the Connector SDK pattern from connect-namecheap.
5
+ * When @hasna/connect-namecheap is published to npm, replace the
6
+ * relative connector import with the package import.
7
+ *
4
8
  * Requires environment variables:
5
9
  * NAMECHEAP_API_KEY — API key from Namecheap
6
10
  * NAMECHEAP_USERNAME — Namecheap account username
7
11
  * NAMECHEAP_CLIENT_IP — Whitelisted client IP address
8
12
  */
9
13
 
14
+ // Re-export types used by consumers (CLI, MCP, registrar)
15
+ export type {
16
+ ConnectorConfig as NamecheapConfig,
17
+ Domain as NamecheapDomain,
18
+ DomainInfo as NamecheapDomainInfo,
19
+ DnsRecord as NamecheapDnsRecord,
20
+ AvailabilityResult as NamecheapAvailability,
21
+ RenewResult as NamecheapRenewResult,
22
+ } from "../../../../../open-connectors/connectors/connect-namecheap/src/index.js";
23
+
24
+ import {
25
+ Connector,
26
+ type ConnectorConfig,
27
+ type Domain as NamecheapDomain,
28
+ type DomainInfo as NamecheapDomainInfo,
29
+ type DnsRecord as NamecheapDnsRecord,
30
+ } from "../../../../../open-connectors/connectors/connect-namecheap/src/index.js";
31
+
10
32
  // ============================================================
11
- // Types
33
+ // Sync Result Type (microservice-specific, not in connector)
12
34
  // ============================================================
13
35
 
14
- export interface NamecheapConfig {
15
- apiKey: string;
16
- username: string;
17
- clientIp: string;
18
- sandbox?: boolean;
19
- }
20
-
21
- export interface NamecheapDomain {
22
- domain: string;
23
- expiry: string;
24
- autoRenew: boolean;
25
- isLocked: boolean;
26
- }
27
-
28
- export interface NamecheapDomainInfo {
29
- domain: string;
30
- registrar: string;
31
- created: string;
32
- expires: string;
33
- nameservers: string[];
34
- }
35
-
36
- export interface NamecheapDnsRecord {
37
- hostId?: string;
38
- type: string;
39
- name: string;
40
- address: string;
41
- mxPref?: number;
42
- ttl: number;
43
- }
44
-
45
- export interface NamecheapAvailability {
46
- domain: string;
47
- available: boolean;
48
- }
49
-
50
- export interface NamecheapRenewResult {
51
- domain: string;
52
- success: boolean;
53
- transactionId?: string;
54
- chargedAmount?: string;
55
- orderId?: string;
56
- }
57
-
58
36
  export interface NamecheapSyncResult {
59
37
  synced: number;
60
38
  errors: string[];
@@ -62,13 +40,14 @@ export interface NamecheapSyncResult {
62
40
  }
63
41
 
64
42
  // ============================================================
65
- // Configuration
43
+ // Connector Instance Management
66
44
  // ============================================================
67
45
 
68
- const API_BASE = "https://api.namecheap.com/xml.response";
69
- const SANDBOX_BASE = "https://api.sandbox.namecheap.com/xml.response";
70
-
71
- export function getConfig(): NamecheapConfig {
46
+ /**
47
+ * Create a Namecheap connector from environment variables.
48
+ * Wraps Connector.fromEnv() with the same env var validation.
49
+ */
50
+ export function getConfig(): ConnectorConfig {
72
51
  const apiKey = process.env["NAMECHEAP_API_KEY"];
73
52
  const username = process.env["NAMECHEAP_USERNAME"];
74
53
  const clientIp = process.env["NAMECHEAP_CLIENT_IP"];
@@ -85,267 +64,72 @@ export function getConfig(): NamecheapConfig {
85
64
  };
86
65
  }
87
66
 
88
- // ============================================================
89
- // XML Parsing Helpers (regex-based, no dependencies)
90
- // ============================================================
91
-
92
- export function extractTag(xml: string, tag: string): string | null {
93
- const regex = new RegExp(`<${tag}[^>]*>([^<]*)</${tag}>`, "i");
94
- const match = xml.match(regex);
95
- return match ? match[1].trim() : null;
96
- }
97
-
98
- export function extractAttribute(xml: string, tag: string, attr: string): string | null {
99
- const regex = new RegExp(`<${tag}\\s[^>]*${attr}="([^"]*)"`, "i");
100
- const match = xml.match(regex);
101
- return match ? match[1] : null;
102
- }
103
-
104
- export function extractAllTags(xml: string, tag: string): string[] {
105
- const regex = new RegExp(`<${tag}(?:\\s[^>]*)?\\/?>(?:([^<]*)<\\/${tag}>)?`, "gi");
106
- const results: string[] = [];
107
- let match;
108
- while ((match = regex.exec(xml)) !== null) {
109
- // Only match exact tag name — skip tags like <DomainListResult> when searching for <Domain>
110
- const fullMatch = match[0];
111
- const tagNameCheck = new RegExp(`^<${tag}(?:\\s|\\/>|>)`, "i");
112
- if (tagNameCheck.test(fullMatch)) {
113
- results.push(fullMatch);
114
- }
115
- }
116
- return results;
117
- }
118
-
119
- export function extractAttributeFromElement(element: string, attr: string): string | null {
120
- const regex = new RegExp(`${attr}="([^"]*)"`, "i");
121
- const match = element.match(regex);
122
- return match ? match[1] : null;
123
- }
124
-
125
- export function checkApiError(xml: string): void {
126
- const status = extractAttribute(xml, "ApiResponse", "Status");
127
- if (status === "ERROR") {
128
- const errorMsg = extractTag(xml, "Message") || extractTag(xml, "Err") || "Unknown Namecheap API error";
129
- const errorNumber = extractAttribute(xml, "Error", "Number") || extractAttribute(xml, "Err", "Number");
130
- throw new Error(`Namecheap API error${errorNumber ? ` (${errorNumber})` : ""}: ${errorMsg}`);
131
- }
67
+ function createConnector(config?: ConnectorConfig): Connector {
68
+ return new Connector(config || getConfig());
132
69
  }
133
70
 
134
71
  // ============================================================
135
- // HTTP Layer
136
- // ============================================================
137
-
138
- export function buildUrl(command: string, config: NamecheapConfig, extraParams?: Record<string, string>): string {
139
- const base = config.sandbox ? SANDBOX_BASE : API_BASE;
140
- const params = new URLSearchParams({
141
- ApiUser: config.username,
142
- ApiKey: config.apiKey,
143
- UserName: config.username,
144
- ClientIp: config.clientIp,
145
- Command: command,
146
- ...extraParams,
147
- });
148
- return `${base}?${params.toString()}`;
149
- }
150
-
151
- export async function apiRequest(command: string, config: NamecheapConfig, extraParams?: Record<string, string>): Promise<string> {
152
- const url = buildUrl(command, config, extraParams);
153
- const response = await fetch(url, {
154
- signal: AbortSignal.timeout(30000),
155
- headers: { "User-Agent": "microservice-domains/0.0.1" },
156
- });
157
-
158
- if (!response.ok) {
159
- throw new Error(`Namecheap API HTTP error: ${response.status} ${response.statusText}`);
160
- }
161
-
162
- const xml = await response.text();
163
- checkApiError(xml);
164
- return xml;
165
- }
166
-
167
- // ============================================================
168
- // API Functions
72
+ // API Functions (thin wrappers around connector)
169
73
  // ============================================================
170
74
 
171
75
  /**
172
76
  * List all domains in the Namecheap account
173
- * Command: namecheap.domains.getList
174
77
  */
175
- export async function listNamecheapDomains(config?: NamecheapConfig): Promise<NamecheapDomain[]> {
176
- const cfg = config || getConfig();
177
- const xml = await apiRequest("namecheap.domains.getList", cfg, {
178
- PageSize: "100",
179
- Page: "1",
180
- });
181
-
182
- const domainElements = extractAllTags(xml, "Domain");
183
- const domains: NamecheapDomain[] = [];
184
-
185
- for (const el of domainElements) {
186
- const name = extractAttributeFromElement(el, "Name");
187
- const expires = extractAttributeFromElement(el, "Expires");
188
- const autoRenew = extractAttributeFromElement(el, "AutoRenew");
189
- const isLocked = extractAttributeFromElement(el, "IsLocked");
190
-
191
- if (name) {
192
- domains.push({
193
- domain: name,
194
- expiry: expires || "",
195
- autoRenew: autoRenew === "true",
196
- isLocked: isLocked === "true",
197
- });
198
- }
199
- }
200
-
201
- return domains;
78
+ export async function listNamecheapDomains(config?: ConnectorConfig): Promise<NamecheapDomain[]> {
79
+ const connector = createConnector(config);
80
+ return connector.domains.list();
202
81
  }
203
82
 
204
83
  /**
205
84
  * Get detailed info for a specific domain
206
- * Command: namecheap.domains.getInfo
207
85
  */
208
- export async function getDomainInfo(domain: string, config?: NamecheapConfig): Promise<NamecheapDomainInfo> {
209
- const cfg = config || getConfig();
210
- const xml = await apiRequest("namecheap.domains.getInfo", cfg, {
211
- DomainName: domain,
212
- });
213
-
214
- // Parse creation and expiry dates
215
- const createdDate = extractTag(xml, "CreatedDate") || extractAttribute(xml, "DomainGetInfoResult", "CreatedDate") || "";
216
- const expiresDate = extractTag(xml, "ExpiredDate") || extractAttribute(xml, "DomainGetInfoResult", "ExpiredDate") || "";
217
-
218
- // Parse nameservers
219
- const nsSection = xml.match(/<DnsDetails[^>]*>([\s\S]*?)<\/DnsDetails>/i);
220
- const nameservers: string[] = [];
221
- if (nsSection) {
222
- const nsElements = nsSection[1].matchAll(/<Nameserver[^>]*>([^<]*)<\/Nameserver>/gi);
223
- for (const m of nsElements) {
224
- if (m[1]) nameservers.push(m[1].trim().toLowerCase());
225
- }
226
- }
227
-
228
- return {
229
- domain,
230
- registrar: "Namecheap",
231
- created: createdDate,
232
- expires: expiresDate,
233
- nameservers,
234
- };
86
+ export async function getDomainInfo(domain: string, config?: ConnectorConfig): Promise<NamecheapDomainInfo> {
87
+ const connector = createConnector(config);
88
+ return connector.domains.getInfo(domain);
235
89
  }
236
90
 
237
91
  /**
238
92
  * Renew a domain
239
- * Command: namecheap.domains.renew
240
93
  */
241
- export async function renewDomain(domain: string, years: number = 1, config?: NamecheapConfig): Promise<NamecheapRenewResult> {
242
- const cfg = config || getConfig();
243
- const xml = await apiRequest("namecheap.domains.renew", cfg, {
244
- DomainName: domain,
245
- Years: String(years),
246
- });
247
-
248
- const transactionId = extractAttribute(xml, "DomainRenewResult", "TransactionID") || undefined;
249
- const chargedAmount = extractAttribute(xml, "DomainRenewResult", "ChargedAmount") || undefined;
250
- const orderId = extractAttribute(xml, "DomainRenewResult", "OrderID") || undefined;
251
-
252
- return {
253
- domain,
254
- success: true,
255
- transactionId,
256
- chargedAmount,
257
- orderId,
258
- };
94
+ export async function renewDomain(domain: string, years: number = 1, config?: ConnectorConfig) {
95
+ const connector = createConnector(config);
96
+ return connector.domains.renew(domain, years);
259
97
  }
260
98
 
261
99
  /**
262
100
  * Get DNS host records for a domain
263
- * Command: namecheap.domains.dns.getHosts
264
101
  */
265
- export async function getDnsRecords(domain: string, sld: string, tld: string, config?: NamecheapConfig): Promise<NamecheapDnsRecord[]> {
266
- const cfg = config || getConfig();
267
- const xml = await apiRequest("namecheap.domains.dns.getHosts", cfg, {
268
- SLD: sld,
269
- TLD: tld,
270
- });
271
-
272
- const hostElements = extractAllTags(xml, "host");
273
- const records: NamecheapDnsRecord[] = [];
274
-
275
- for (const el of hostElements) {
276
- const type = extractAttributeFromElement(el, "Type");
277
- const name = extractAttributeFromElement(el, "Name");
278
- const address = extractAttributeFromElement(el, "Address");
279
- const hostId = extractAttributeFromElement(el, "HostId");
280
- const mxPref = extractAttributeFromElement(el, "MXPref");
281
- const ttl = extractAttributeFromElement(el, "TTL");
282
-
283
- if (type && name && address) {
284
- records.push({
285
- hostId: hostId || undefined,
286
- type,
287
- name,
288
- address,
289
- mxPref: mxPref ? parseInt(mxPref) : undefined,
290
- ttl: ttl ? parseInt(ttl) : 1800,
291
- });
292
- }
293
- }
294
-
295
- return records;
102
+ export async function getDnsRecords(domain: string, sld: string, tld: string, config?: ConnectorConfig): Promise<NamecheapDnsRecord[]> {
103
+ const connector = createConnector(config);
104
+ return connector.dns.getHosts(sld, tld);
296
105
  }
297
106
 
298
107
  /**
299
108
  * Set DNS host records for a domain
300
- * Command: namecheap.domains.dns.setHosts
301
109
  */
302
110
  export async function setDnsRecords(
303
111
  domain: string,
304
112
  sld: string,
305
113
  tld: string,
306
114
  records: NamecheapDnsRecord[],
307
- config?: NamecheapConfig
115
+ config?: ConnectorConfig
308
116
  ): Promise<boolean> {
309
- const cfg = config || getConfig();
310
- const params: Record<string, string> = {
311
- SLD: sld,
312
- TLD: tld,
313
- };
314
-
315
- for (let i = 0; i < records.length; i++) {
316
- const r = records[i];
317
- const idx = i + 1;
318
- params[`HostName${idx}`] = r.name;
319
- params[`RecordType${idx}`] = r.type;
320
- params[`Address${idx}`] = r.address;
321
- params[`TTL${idx}`] = String(r.ttl);
322
- if (r.mxPref !== undefined) {
323
- params[`MXPref${idx}`] = String(r.mxPref);
324
- }
325
- }
326
-
327
- await apiRequest("namecheap.domains.dns.setHosts", cfg, params);
328
- return true;
117
+ const connector = createConnector(config);
118
+ return connector.dns.setHosts(sld, tld, records);
329
119
  }
330
120
 
331
121
  /**
332
122
  * Check domain availability
333
- * Command: namecheap.domains.check
334
123
  */
335
- export async function checkAvailability(domain: string, config?: NamecheapConfig): Promise<NamecheapAvailability> {
336
- const cfg = config || getConfig();
337
- const xml = await apiRequest("namecheap.domains.check", cfg, {
338
- DomainList: domain,
339
- });
340
-
341
- const available = extractAttribute(xml, "DomainCheckResult", "Available");
342
-
343
- return {
344
- domain,
345
- available: available === "true",
346
- };
124
+ export async function checkAvailability(domain: string, config?: ConnectorConfig) {
125
+ const connector = createConnector(config);
126
+ return connector.domains.check(domain);
347
127
  }
348
128
 
129
+ // ============================================================
130
+ // Domain Helpers (microservice-specific)
131
+ // ============================================================
132
+
349
133
  /**
350
134
  * Split a domain name into SLD (second-level domain) and TLD (top-level domain)
351
135
  */
@@ -367,6 +151,10 @@ export function splitDomain(domain: string): { sld: string; tld: string } {
367
151
  };
368
152
  }
369
153
 
154
+ // ============================================================
155
+ // Sync to Local DB (microservice business logic)
156
+ // ============================================================
157
+
370
158
  /**
371
159
  * Sync domains from Namecheap to local database
372
160
  * Calls listDomains + getDomainInfo for each, upserts into local domains table
@@ -393,23 +181,23 @@ export async function syncToLocalDb(dbFunctions: {
393
181
  nameservers?: string[];
394
182
  }
395
183
  ) => unknown;
396
- }, config?: NamecheapConfig): Promise<NamecheapSyncResult> {
397
- const cfg = config || getConfig();
184
+ }, config?: ConnectorConfig): Promise<NamecheapSyncResult> {
185
+ const connector = createConnector(config);
398
186
  const result: NamecheapSyncResult = { synced: 0, errors: [], domains: [] };
399
187
 
400
188
  let ncDomains: NamecheapDomain[];
401
189
  try {
402
- ncDomains = await listNamecheapDomains(cfg);
190
+ ncDomains = await connector.domains.list();
403
191
  } catch (error) {
404
192
  throw new Error(`Failed to list Namecheap domains: ${error instanceof Error ? error.message : String(error)}`);
405
193
  }
406
194
 
407
195
  for (const ncDomain of ncDomains) {
408
196
  try {
409
- // Get detailed info
197
+ // Get detailed info via connector
410
198
  let info: NamecheapDomainInfo;
411
199
  try {
412
- info = await getDomainInfo(ncDomain.domain, cfg);
200
+ info = await connector.domains.getInfo(ncDomain.domain);
413
201
  } catch {
414
202
  // Fall back to basic info if getInfo fails
415
203
  info = {
@@ -5,7 +5,8 @@
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "microservice-social": "./src/cli/index.ts",
8
- "microservice-social-mcp": "./src/mcp/index.ts"
8
+ "microservice-social-mcp": "./src/mcp/index.ts",
9
+ "microservice-social-serve": "./src/server/index.ts"
9
10
  },
10
11
  "exports": {
11
12
  ".": "./src/index.ts"