domain-suggester 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +287 -0
- package/bin/cli.js +56 -0
- package/index.js +2 -0
- package/lib/client.js +120 -0
- package/lib/errors.js +54 -0
- package/package.json +34 -0
package/README.md
ADDED
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
# domain-suggester
|
|
2
|
+
|
|
3
|
+
Node.js SDK for the DomainHub Domain Suggester API.
|
|
4
|
+
|
|
5
|
+
This package is a thin client for the hosted API. It does **not** contain the private scoring engine or internal domain suggestion logic.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install domain-suggester
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Requirements
|
|
14
|
+
|
|
15
|
+
- Node.js 18 or newer
|
|
16
|
+
- A valid API key
|
|
17
|
+
|
|
18
|
+
## Authentication
|
|
19
|
+
|
|
20
|
+
Set your API key as an environment variable:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
export DOMAIN_SUGGESTER_API_KEY="dsg_live_your_api_key_here"
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Quick start
|
|
27
|
+
|
|
28
|
+
```js
|
|
29
|
+
const { createClient } = require("@domainhub/domain-suggester");
|
|
30
|
+
|
|
31
|
+
async function main() {
|
|
32
|
+
const client = createClient({
|
|
33
|
+
apiKey: process.env.DOMAIN_SUGGESTER_API_KEY,
|
|
34
|
+
baseUrl: "https://suggest.domainhub.sbs"
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const job = await client.createJob({
|
|
38
|
+
domains: [
|
|
39
|
+
"example.com",
|
|
40
|
+
"domain.com"
|
|
41
|
+
],
|
|
42
|
+
config: {
|
|
43
|
+
prefixes: ["get", "my", "try"],
|
|
44
|
+
tlds: {
|
|
45
|
+
com: 0,
|
|
46
|
+
net: 1,
|
|
47
|
+
io: 2
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
options: {
|
|
51
|
+
topN: 25
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
console.log("Submitted job:", job);
|
|
56
|
+
|
|
57
|
+
const result = await client.waitForResult(job.jobId, {
|
|
58
|
+
intervalMs: 3000,
|
|
59
|
+
timeoutMs: 10 * 60 * 1000
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
console.log(JSON.stringify(result, null, 2));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
main().catch((err) => {
|
|
66
|
+
console.error(err);
|
|
67
|
+
process.exit(1);
|
|
68
|
+
});
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Creating a client
|
|
72
|
+
|
|
73
|
+
```js
|
|
74
|
+
const { createClient } = require("@domainhub/domain-suggester");
|
|
75
|
+
|
|
76
|
+
const client = createClient({
|
|
77
|
+
apiKey: process.env.DOMAIN_SUGGESTER_API_KEY,
|
|
78
|
+
baseUrl: "https://suggest.domainhub.sbs"
|
|
79
|
+
});
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Options
|
|
83
|
+
|
|
84
|
+
- `apiKey` — required, your customer API key
|
|
85
|
+
- `baseUrl` — optional, defaults to your API base URL if you set one in the SDK
|
|
86
|
+
|
|
87
|
+
## API
|
|
88
|
+
|
|
89
|
+
### `createClient(options)`
|
|
90
|
+
|
|
91
|
+
Creates a new SDK client.
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
### `client.createJob(payload)`
|
|
96
|
+
|
|
97
|
+
Creates a new suggestion job.
|
|
98
|
+
|
|
99
|
+
#### Example
|
|
100
|
+
|
|
101
|
+
```js
|
|
102
|
+
const job = await client.createJob({
|
|
103
|
+
domains: ["example.com", "domain.com"],
|
|
104
|
+
config: {
|
|
105
|
+
prefixes: ["get", "my", "try"],
|
|
106
|
+
tlds: { com: 0, net: 1, io: 2 }
|
|
107
|
+
},
|
|
108
|
+
options: {
|
|
109
|
+
topN: 25,
|
|
110
|
+
debug: false,
|
|
111
|
+
ideal: "example"
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
#### Payload shape
|
|
117
|
+
|
|
118
|
+
```js
|
|
119
|
+
{
|
|
120
|
+
domains: string[],
|
|
121
|
+
config: {
|
|
122
|
+
prefixes: string[],
|
|
123
|
+
tlds: Record<string, number>
|
|
124
|
+
},
|
|
125
|
+
options: {
|
|
126
|
+
topN?: number,
|
|
127
|
+
debug?: boolean,
|
|
128
|
+
ideal?: string,
|
|
129
|
+
target?: string
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
### `client.getJob(jobId)`
|
|
137
|
+
|
|
138
|
+
Returns job status.
|
|
139
|
+
|
|
140
|
+
#### Example
|
|
141
|
+
|
|
142
|
+
```js
|
|
143
|
+
const status = await client.getJob(jobId);
|
|
144
|
+
console.log(status);
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
### `client.getResult(jobId)`
|
|
150
|
+
|
|
151
|
+
Returns the final job result once completed.
|
|
152
|
+
|
|
153
|
+
#### Example
|
|
154
|
+
|
|
155
|
+
```js
|
|
156
|
+
const result = await client.getResult(jobId);
|
|
157
|
+
console.log(result);
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
### `client.listJobs()`
|
|
163
|
+
|
|
164
|
+
Returns the current customer's recent jobs.
|
|
165
|
+
|
|
166
|
+
#### Example
|
|
167
|
+
|
|
168
|
+
```js
|
|
169
|
+
const jobs = await client.listJobs();
|
|
170
|
+
console.log(jobs);
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
### `client.me()`
|
|
176
|
+
|
|
177
|
+
Returns current API key profile and usage information.
|
|
178
|
+
|
|
179
|
+
#### Example
|
|
180
|
+
|
|
181
|
+
```js
|
|
182
|
+
const me = await client.me();
|
|
183
|
+
console.log(me);
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
### `client.waitForResult(jobId, options)`
|
|
189
|
+
|
|
190
|
+
Polls until the job completes or fails.
|
|
191
|
+
|
|
192
|
+
#### Example
|
|
193
|
+
|
|
194
|
+
```js
|
|
195
|
+
const result = await client.waitForResult(jobId, {
|
|
196
|
+
intervalMs: 3000,
|
|
197
|
+
timeoutMs: 10 * 60 * 1000
|
|
198
|
+
});
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
#### Options
|
|
202
|
+
|
|
203
|
+
- `intervalMs` — polling interval in milliseconds
|
|
204
|
+
- `timeoutMs` — max total wait time in milliseconds
|
|
205
|
+
|
|
206
|
+
## Example response
|
|
207
|
+
|
|
208
|
+
### Job created
|
|
209
|
+
|
|
210
|
+
```json
|
|
211
|
+
{
|
|
212
|
+
"ok": true,
|
|
213
|
+
"jobId": "123",
|
|
214
|
+
"status": "queued",
|
|
215
|
+
"pollUrl": "/v1/jobs/123",
|
|
216
|
+
"resultUrl": "/v1/jobs/123/result"
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### Job status
|
|
221
|
+
|
|
222
|
+
```json
|
|
223
|
+
{
|
|
224
|
+
"ok": true,
|
|
225
|
+
"jobId": "123",
|
|
226
|
+
"name": "suggest",
|
|
227
|
+
"state": "active",
|
|
228
|
+
"progress": 15,
|
|
229
|
+
"submittedAt": "2026-03-26T12:00:00.000Z",
|
|
230
|
+
"finishedOn": null,
|
|
231
|
+
"failedReason": null
|
|
232
|
+
}
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### Job result
|
|
236
|
+
|
|
237
|
+
```json
|
|
238
|
+
{
|
|
239
|
+
"ok": true,
|
|
240
|
+
"jobId": "123",
|
|
241
|
+
"state": "completed",
|
|
242
|
+
"result": {
|
|
243
|
+
"count": 25,
|
|
244
|
+
"durationMs": 8123,
|
|
245
|
+
"sortedDomains": [
|
|
246
|
+
"getexample.com",
|
|
247
|
+
"myexample.com"
|
|
248
|
+
],
|
|
249
|
+
"detailedResults": [
|
|
250
|
+
{
|
|
251
|
+
"domain": "getexample.com",
|
|
252
|
+
"score": 12.34
|
|
253
|
+
}
|
|
254
|
+
]
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
## Error handling
|
|
260
|
+
|
|
261
|
+
The SDK throws when the API returns a non-2xx response.
|
|
262
|
+
|
|
263
|
+
```js
|
|
264
|
+
try {
|
|
265
|
+
await client.createJob(payload);
|
|
266
|
+
} catch (err) {
|
|
267
|
+
console.error(err.message);
|
|
268
|
+
console.error(err.status);
|
|
269
|
+
console.error(err.data);
|
|
270
|
+
}
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
## Security
|
|
274
|
+
|
|
275
|
+
- Keep your API key secret
|
|
276
|
+
- Never expose your API key in frontend/browser code
|
|
277
|
+
- Use this SDK only in trusted backend or local Node.js environments
|
|
278
|
+
|
|
279
|
+
## Notes
|
|
280
|
+
|
|
281
|
+
- This SDK is only a transport client
|
|
282
|
+
- The private suggestion engine runs on the hosted service
|
|
283
|
+
- Request options such as `prefixes`, `tlds`, and `topN` are passed through to the API
|
|
284
|
+
|
|
285
|
+
## Support
|
|
286
|
+
|
|
287
|
+
For support, contact DomainHub.
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// bin/cli.js
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const { Command } = require("commander");
|
|
5
|
+
|
|
6
|
+
const program = new Command();
|
|
7
|
+
|
|
8
|
+
program
|
|
9
|
+
.name("domain-suggester")
|
|
10
|
+
.description("CLI for the hosted domain suggester API")
|
|
11
|
+
.version("1.0.0");
|
|
12
|
+
|
|
13
|
+
program
|
|
14
|
+
.command("suggest")
|
|
15
|
+
.argument("<file>", "path to txt file with domains")
|
|
16
|
+
.option("--top <number>", "top N results", "25")
|
|
17
|
+
.option("--api-url <url>", "API base URL", "https://suggest.domainhub.sbs")
|
|
18
|
+
.action(async (file, options) => {
|
|
19
|
+
const apiKey = process.env.DOMAIN_SUGGESTER_API_KEY;
|
|
20
|
+
|
|
21
|
+
if (!apiKey) {
|
|
22
|
+
console.error("Missing DOMAIN_SUGGESTER_API_KEY");
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const raw = fs.readFileSync(file, "utf8");
|
|
27
|
+
const domains = raw
|
|
28
|
+
.split(/\r?\n/)
|
|
29
|
+
.map(x => x.trim())
|
|
30
|
+
.filter(Boolean);
|
|
31
|
+
|
|
32
|
+
const res = await fetch(`${options.apiUrl}/v1/jobs`, {
|
|
33
|
+
method: "POST",
|
|
34
|
+
headers: {
|
|
35
|
+
"content-type": "application/json",
|
|
36
|
+
"authorization": `Bearer ${apiKey}`
|
|
37
|
+
},
|
|
38
|
+
body: JSON.stringify({
|
|
39
|
+
domains,
|
|
40
|
+
config: {
|
|
41
|
+
prefixes: ["get", "my", "try"],
|
|
42
|
+
tlds: { com: 0, net: 1, io: 2 }
|
|
43
|
+
},
|
|
44
|
+
options: {
|
|
45
|
+
topN: Number(options.top)
|
|
46
|
+
}
|
|
47
|
+
})
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const text = await res.text();
|
|
51
|
+
console.log(text);
|
|
52
|
+
|
|
53
|
+
if (!res.ok) process.exit(1);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
program.parse();
|
package/index.js
ADDED
package/lib/client.js
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
// lib/client.js
|
|
2
|
+
const {
|
|
3
|
+
AuthenticationError,
|
|
4
|
+
RateLimitError,
|
|
5
|
+
ValidationError,
|
|
6
|
+
NotFoundError,
|
|
7
|
+
ApiError
|
|
8
|
+
} = require("./errors");
|
|
9
|
+
|
|
10
|
+
class DomainSuggesterClient {
|
|
11
|
+
constructor({ apiKey, baseUrl = "https://suggest.domainhub.sbs" }) {
|
|
12
|
+
if (!apiKey) {
|
|
13
|
+
throw new Error("Missing apiKey");
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (typeof baseUrl !== "string" || !baseUrl) {
|
|
17
|
+
throw new Error("Missing or invalid baseUrl");
|
|
18
|
+
}
|
|
19
|
+
this.apiKey = apiKey;
|
|
20
|
+
this.baseUrl = baseUrl.replace(/\/+$/, "");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async request(path, options = {}) {
|
|
24
|
+
const res = await fetch(`${this.baseUrl}${path}`, {
|
|
25
|
+
...options,
|
|
26
|
+
headers: {
|
|
27
|
+
authorization: `Bearer ${this.apiKey}`,
|
|
28
|
+
"content-type": "application/json",
|
|
29
|
+
...(options.headers || {})
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const text = await res.text();
|
|
34
|
+
let data;
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
data = text ? JSON.parse(text) : null;
|
|
38
|
+
} catch {
|
|
39
|
+
data = { raw: text };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (!res.ok) {
|
|
43
|
+
const message = data?.error || `Request failed with ${res.status}`;
|
|
44
|
+
const options = { status: res.status, data };
|
|
45
|
+
|
|
46
|
+
if (res.status === 400) throw new ValidationError(message, options);
|
|
47
|
+
if (res.status === 401) throw new AuthenticationError(message, options);
|
|
48
|
+
if (res.status === 404) throw new NotFoundError(message, options);
|
|
49
|
+
if (res.status === 429) throw new RateLimitError(message, options);
|
|
50
|
+
|
|
51
|
+
throw new ApiError(message, options);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return data;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async createJob(payload) {
|
|
58
|
+
return this.request("/v1/jobs", {
|
|
59
|
+
method: "POST",
|
|
60
|
+
body: JSON.stringify(payload)
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async getJob(jobId) {
|
|
65
|
+
return this.request(`/v1/jobs/${jobId}`, {
|
|
66
|
+
method: "GET"
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async getResult(jobId) {
|
|
71
|
+
return this.request(`/v1/jobs/${jobId}/result`, {
|
|
72
|
+
method: "GET"
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async listJobs() {
|
|
77
|
+
return this.request("/v1/jobs", {
|
|
78
|
+
method: "GET"
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async me() {
|
|
83
|
+
return this.request("/v1/me", {
|
|
84
|
+
method: "GET"
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async waitForResult(jobId, { intervalMs = 3000, timeoutMs = 10 * 60 * 1000 } = {}) {
|
|
89
|
+
const started = Date.now();
|
|
90
|
+
|
|
91
|
+
while (true) {
|
|
92
|
+
const job = await this.getJob(jobId);
|
|
93
|
+
|
|
94
|
+
if (job.state === "completed") {
|
|
95
|
+
return this.getResult(jobId);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (job.state === "failed") {
|
|
99
|
+
const err = new Error(job.failedReason || "Job failed");
|
|
100
|
+
err.job = job;
|
|
101
|
+
throw err;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (Date.now() - started > timeoutMs) {
|
|
105
|
+
throw new Error("Timed out waiting for job result");
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function createClient(options) {
|
|
114
|
+
return new DomainSuggesterClient(options);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
module.exports = {
|
|
118
|
+
createClient,
|
|
119
|
+
DomainSuggesterClient
|
|
120
|
+
};
|
package/lib/errors.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// lib/errors.js
|
|
2
|
+
class DomainSuggesterError extends Error {
|
|
3
|
+
constructor(message, options = {}) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.name = "DomainSuggesterError";
|
|
6
|
+
this.status = options.status || null;
|
|
7
|
+
this.code = options.code || null;
|
|
8
|
+
this.data = options.data || null;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
class AuthenticationError extends DomainSuggesterError {
|
|
13
|
+
constructor(message = "Unauthorized", options = {}) {
|
|
14
|
+
super(message, { ...options, status: options.status || 401 });
|
|
15
|
+
this.name = "AuthenticationError";
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
class RateLimitError extends DomainSuggesterError {
|
|
20
|
+
constructor(message = "Rate limit exceeded", options = {}) {
|
|
21
|
+
super(message, { ...options, status: options.status || 429 });
|
|
22
|
+
this.name = "RateLimitError";
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
class ValidationError extends DomainSuggesterError {
|
|
27
|
+
constructor(message = "Validation failed", options = {}) {
|
|
28
|
+
super(message, { ...options, status: options.status || 400 });
|
|
29
|
+
this.name = "ValidationError";
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
class NotFoundError extends DomainSuggesterError {
|
|
34
|
+
constructor(message = "Not found", options = {}) {
|
|
35
|
+
super(message, { ...options, status: options.status || 404 });
|
|
36
|
+
this.name = "NotFoundError";
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
class ApiError extends DomainSuggesterError {
|
|
41
|
+
constructor(message = "API request failed", options = {}) {
|
|
42
|
+
super(message, options);
|
|
43
|
+
this.name = "ApiError";
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
module.exports = {
|
|
48
|
+
DomainSuggesterError,
|
|
49
|
+
AuthenticationError,
|
|
50
|
+
RateLimitError,
|
|
51
|
+
ValidationError,
|
|
52
|
+
NotFoundError,
|
|
53
|
+
ApiError
|
|
54
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "domain-suggester",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Node.js SDK for the DomainHub domain suggester API",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"domain-suggester": "./bin/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"type": "commonjs",
|
|
10
|
+
"files": [
|
|
11
|
+
"index.js",
|
|
12
|
+
"bin",
|
|
13
|
+
"lib",
|
|
14
|
+
"README.md"
|
|
15
|
+
],
|
|
16
|
+
"keywords": [
|
|
17
|
+
"domain",
|
|
18
|
+
"sdk",
|
|
19
|
+
"api",
|
|
20
|
+
"domain-suggester",
|
|
21
|
+
"domainhub"
|
|
22
|
+
],
|
|
23
|
+
"author": "DomainHub",
|
|
24
|
+
"license": "UNLICENSED",
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=18"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"commander": "^12.1.0"
|
|
30
|
+
},
|
|
31
|
+
"publishConfig": {
|
|
32
|
+
"access": "public"
|
|
33
|
+
}
|
|
34
|
+
}
|