@hyphen/sdk 1.7.0 → 1.9.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 CHANGED
@@ -11,7 +11,7 @@ The Hyphen Node.js SDK is a JavaScript library that allows developers to easily
11
11
 
12
12
  # Table of Contents
13
13
  - [Installation](#installation)
14
- - [Basic Usage](#basic-usage)
14
+ - [Basic Usage with Hyphen](#basic-usage-with-hyphen)
15
15
  - [Toggle](#toggle)
16
16
  - [Toggle Options](#toggle-options)
17
17
  - [Toggle API](#toggle-api)
@@ -35,9 +35,93 @@ To install the Hyphen Node.js SDK, you can use npm or yarn. Run the following co
35
35
  npm install @hyphen/sdk
36
36
  ```
37
37
 
38
- # Basic Usage
38
+ # Basic Usage with Hyphen
39
39
 
40
- There are many ways to use the Hyphen Node.js SDK. Because of this we have created examples for each of the different ways in each secton of the documentation.
40
+ There are many ways to use the Hyphen Node.js SDK. Because of this we have created examples for each of the different ways in each secton of the documentation. To get started, you can create an instance of the `Hyphen` class and use its methods to interact with the various services.
41
+
42
+ ```javascript
43
+ import { Hyphen } from '@hyphen/sdk';
44
+
45
+ const hyphen = new Hyphen({
46
+ publicApiKey: 'your_public_api_key',
47
+ applicationId: 'your_application_id',
48
+ });
49
+ const result = await hyphen.toggle.getBoolean('hyphen-sdk-boolean', false);
50
+ console.log('Boolean toggle value:', result); // true
51
+ ```
52
+
53
+ You can also use `netInfo` to access the network information service.
54
+
55
+ ```javascript
56
+ import { Hyphen, ToggleContext } from '@hyphen/sdk';
57
+
58
+ const context: ToggleContext = {
59
+ targetingKey: 'user-123',
60
+ ipAddress: '203.0.113.42',
61
+ customAttributes: {
62
+ subscriptionLevel: 'premium',
63
+ region: 'us-east',
64
+ },
65
+ user: {
66
+ id: 'user-123',
67
+ email: 'john.doe@example.com',
68
+ name: 'John Doe',
69
+ customAttributes: {
70
+ role: 'admin',
71
+ },
72
+ },
73
+ };
74
+
75
+ const hyphen = new Hyphen({
76
+ publicApiKey: 'your_public_api_key',
77
+ toggle: {
78
+ applicationId: 'your_application_id',
79
+ context: context,
80
+ },
81
+ });
82
+ const result = await hyphen.toggle.getBoolean('hyphen-sdk-boolean', false);
83
+ console.log('Boolean toggle value:', result); // true
84
+ ```
85
+
86
+ You can also use `netInfo` to access the network information service.
87
+
88
+ ```javascript
89
+ import { Hyphen } from '@hyphen/sdk';
90
+
91
+ const hyphen = new Hyphen({
92
+ apiKey: 'your_api_key',
93
+ });
94
+ const result = await hyphen.netInfo.getIpInfo('8.8.8.8');
95
+ console.log('Geo IP information:', result);
96
+ ```
97
+
98
+ If you need to set the `apiKey` or `publicApiKey` after initializing `Hyphen` you can do so and it will cascade the setting to the individual services:
99
+
100
+ ```javascript
101
+ import { Hyphen } from '@hyphen/sdk';
102
+
103
+ const hyphen = new Hyphen();
104
+ hyphen.apiKey = 'your_api_key';
105
+ const result = await hyphen.netInfo.getIpInfo('8.8.8.8');
106
+ console.log('Geo IP information:', result);
107
+ ```
108
+
109
+ Finally, `error`, `warn`, and `info` emitters are enabled from each of the services and you can access them by doing the following example with `error`:
110
+
111
+ ```javascript
112
+ import { Hyphen } from '@hyphen/sdk';
113
+
114
+ const hyphen = new Hyphen({ apiKey: 'your_api_key'});
115
+
116
+ hyphen.on('error', (error) => {
117
+ console.log(error);
118
+ });
119
+
120
+ const result = await hyphen.netInfo.getIpInfo('8.8.8.8');
121
+ console.log('Geo IP information:', result);
122
+ ```
123
+
124
+ The rest of the examples for each service show you accessing the service instance directly.
41
125
 
42
126
  # Toggle
43
127
 
@@ -532,11 +616,12 @@ import { loadEnv } from '@hyphen/sdk';
532
616
  loadEnv({ local: false });
533
617
  ```
534
618
 
535
- # Net Info (https://net.info)
619
+ # Net Info
536
620
 
537
621
  The Hyphen Node.js SDK also provides a `NetInfo` class that allows you to fetch geo information about an IP address. This can be useful for debugging or logging purposes. You can read more about it:
538
622
 
539
623
  * [Website](https://hyphen.ai/net-info)
624
+ * [https://net.info](https://net.info) - webservice uri used by the SDK
540
625
  * [Quick Start Guide](https://docs.hyphen.ai/docs/netinfo-quickstart)
541
626
 
542
627
  To use the `NetInfo` class, you can do the following:
@@ -551,6 +636,18 @@ const ipInfo = await netInfo.getIpInfo('8.8.8.8');
551
636
  console.log('IP Info:', ipInfo);
552
637
  ```
553
638
 
639
+ If you want to fetch information for multiple IP addresses, you can do it like this:
640
+
641
+ ```javascript
642
+ import { NetInfo } from '@hyphen/sdk';
643
+ const netInfo = new NetInfo({
644
+ apiKey: 'your_api_key',
645
+ });
646
+ const ips = ['8.8.8.8', '1.1.1.1'];
647
+ const ipInfos = await netInfo.getIpInfos(ips);
648
+ console.log('IP Infos:', ipInfos);
649
+ ```
650
+
554
651
  You can also set the API key using the `HYPHEN_API_KEY` environment variable. This is useful for keeping your API key secure and not hardcoding it in your code.
555
652
 
556
653
  # Contributing
package/dist/index.cjs CHANGED
@@ -31,6 +31,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  // src/index.ts
32
32
  var index_exports = {};
33
33
  __export(index_exports, {
34
+ Hyphen: () => Hyphen,
34
35
  Toggle: () => Toggle,
35
36
  ToggleHooks: () => ToggleHooks,
36
37
  loadEnv: () => loadEnv
@@ -442,8 +443,289 @@ function loadEnv(options) {
442
443
  }
443
444
  }
444
445
  __name(loadEnv, "loadEnv");
446
+
447
+ // src/hyphen.ts
448
+ var import_hookified3 = require("hookified");
449
+
450
+ // src/net-info.ts
451
+ var import_node_process3 = __toESM(require("process"), 1);
452
+
453
+ // src/base-service.ts
454
+ var import_hookified2 = require("hookified");
455
+ var import_cacheable = require("cacheable");
456
+ var import_axios = __toESM(require("axios"), 1);
457
+ var import_pino = __toESM(require("pino"), 1);
458
+ var ErrorMessages = /* @__PURE__ */ function(ErrorMessages2) {
459
+ ErrorMessages2["API_KEY_REQUIRED"] = "API key is required. Please provide it via options or set the HYPHEN_API_KEY environment variable.";
460
+ ErrorMessages2["PUBLIC_API_KEY_SHOULD_NOT_BE_USED"] = "The provided API key is a public API key. Please provide a valid non public API key for authentication.";
461
+ return ErrorMessages2;
462
+ }({});
463
+ var BaseService = class extends import_hookified2.Hookified {
464
+ static {
465
+ __name(this, "BaseService");
466
+ }
467
+ _log = (0, import_pino.default)();
468
+ _cache = new import_cacheable.Cacheable();
469
+ _throwErrors = false;
470
+ constructor(options) {
471
+ super(options);
472
+ if (options && options.throwErrors !== void 0) {
473
+ this._throwErrors = options.throwErrors;
474
+ }
475
+ }
476
+ get log() {
477
+ return this._log;
478
+ }
479
+ set log(value) {
480
+ this._log = value;
481
+ }
482
+ get cache() {
483
+ return this._cache;
484
+ }
485
+ set cache(value) {
486
+ this._cache = value;
487
+ }
488
+ get throwErrors() {
489
+ return this._throwErrors;
490
+ }
491
+ set throwErrors(value) {
492
+ this._throwErrors = value;
493
+ }
494
+ error(message, ...args) {
495
+ this._log.error(message, ...args);
496
+ this.emit("error", message, ...args);
497
+ if (this.throwErrors) {
498
+ throw new Error(message);
499
+ }
500
+ }
501
+ warn(message, ...args) {
502
+ this._log.warn(message, ...args);
503
+ this.emit("warn", message, ...args);
504
+ }
505
+ info(message, ...args) {
506
+ this._log.info(message, ...args);
507
+ this.emit("info", message, ...args);
508
+ }
509
+ async get(url, config2) {
510
+ return import_axios.default.get(url, config2);
511
+ }
512
+ async post(url, data, config2) {
513
+ return import_axios.default.post(url, data, config2);
514
+ }
515
+ async put(url, data, config2) {
516
+ return import_axios.default.put(url, data, config2);
517
+ }
518
+ async delete(url, config2) {
519
+ return import_axios.default.delete(url, config2);
520
+ }
521
+ async patch(url, data, config2) {
522
+ return import_axios.default.patch(url, data, config2);
523
+ }
524
+ createHeaders(apiKey) {
525
+ const headers = {
526
+ "content-type": "application/json",
527
+ accept: "application/json"
528
+ };
529
+ if (apiKey) {
530
+ headers["x-api-key"] = apiKey;
531
+ }
532
+ return headers;
533
+ }
534
+ };
535
+
536
+ // src/net-info.ts
537
+ loadEnv();
538
+ var NetInfo = class extends BaseService {
539
+ static {
540
+ __name(this, "NetInfo");
541
+ }
542
+ _apiKey;
543
+ _baseUri = "https://net.info";
544
+ constructor(options) {
545
+ super(options);
546
+ if (options?.baseUri) {
547
+ this._baseUri = options.baseUri;
548
+ }
549
+ this.setApiKey(options?.apiKey);
550
+ if (!this._apiKey && import_node_process3.default.env.HYPHEN_API_KEY) {
551
+ this.setApiKey(import_node_process3.default.env.HYPHEN_API_KEY);
552
+ }
553
+ if (!this._apiKey) {
554
+ this.error(ErrorMessages.API_KEY_REQUIRED);
555
+ }
556
+ }
557
+ /**
558
+ * Gets or sets the API key for authentication.
559
+ * If not set, it will try to use the `HYPHEN_API_KEY` environment variable.
560
+ * @type {string | undefined}
561
+ */
562
+ get apiKey() {
563
+ return this._apiKey;
564
+ }
565
+ /**
566
+ * Sets the API key for authentication.
567
+ * @param {string | undefined} value - The API key to set.
568
+ */
569
+ set apiKey(value) {
570
+ this.setApiKey(value);
571
+ }
572
+ /**
573
+ * Gets or sets the base URI for the API.
574
+ * @type {string}
575
+ */
576
+ get baseUri() {
577
+ return this._baseUri;
578
+ }
579
+ /**
580
+ * Sets the base URI for the API.
581
+ * @param {string} value - The base URI to set.
582
+ */
583
+ set baseUri(value) {
584
+ this._baseUri = value;
585
+ }
586
+ setApiKey(value) {
587
+ if (value?.startsWith("public_")) {
588
+ this.error(ErrorMessages.PUBLIC_API_KEY_SHOULD_NOT_BE_USED);
589
+ return;
590
+ }
591
+ this._apiKey = value;
592
+ }
593
+ /**
594
+ * Fetches GeoIP information for a given IP address.
595
+ * @param {string} ip - The IP address to fetch GeoIP information for.
596
+ * @returns {Promise<ipInfo | ipInfoError>} - A promise that resolves to the ip information or an error.
597
+ */
598
+ async getIpInfo(ip) {
599
+ try {
600
+ if (!this._apiKey) {
601
+ throw new Error(ErrorMessages.API_KEY_REQUIRED);
602
+ }
603
+ const url = `${this._baseUri}/ip/${ip}`;
604
+ const headers = this.createHeaders(this._apiKey);
605
+ const response = await this.get(url, {
606
+ headers
607
+ });
608
+ if (response.status !== 200) {
609
+ const errorResult = {
610
+ ip,
611
+ type: "error",
612
+ errorMessage: `Failed to fetch ip info: ${response.statusText}`
613
+ };
614
+ return errorResult;
615
+ }
616
+ return response.data;
617
+ } catch (error) {
618
+ this.error(`Failed to fetch ip info: ${error instanceof Error ? error.message : "Unknown error"}`);
619
+ const errorResult = {
620
+ ip,
621
+ type: "error",
622
+ errorMessage: error instanceof Error ? error.message : "Unknown error"
623
+ };
624
+ return errorResult;
625
+ }
626
+ }
627
+ async getIpInfos(ips) {
628
+ if (!Array.isArray(ips) || ips.length === 0) {
629
+ this.error("The provided IPs array is invalid. It should be a non-empty array of strings.");
630
+ return [];
631
+ }
632
+ const errorResults = [];
633
+ try {
634
+ if (!this._apiKey) {
635
+ throw new Error(ErrorMessages.API_KEY_REQUIRED);
636
+ }
637
+ const url = `${this._baseUri}/ip`;
638
+ const headers = this.createHeaders(this._apiKey);
639
+ const response = await this.post(url, ips, {
640
+ headers
641
+ });
642
+ if (response.status !== 200) {
643
+ errorResults.push({
644
+ ip: "",
645
+ type: "error",
646
+ errorMessage: `Failed to fetch ip infos: ${response.statusText}`
647
+ });
648
+ return errorResults;
649
+ }
650
+ const responseData = response?.data;
651
+ return responseData.data;
652
+ } catch (error) {
653
+ this.error(`Failed to fetch ip infos: ${error instanceof Error ? error.message : "Unknown error"}`);
654
+ errorResults.push({
655
+ ip: "",
656
+ type: "error",
657
+ errorMessage: error instanceof Error ? error.message : "Unknown error"
658
+ });
659
+ return errorResults;
660
+ }
661
+ }
662
+ };
663
+
664
+ // src/hyphen.ts
665
+ var Hyphen = class extends import_hookified3.Hookified {
666
+ static {
667
+ __name(this, "Hyphen");
668
+ }
669
+ _netInfo;
670
+ _toggle;
671
+ _publicApiKey;
672
+ _apiKey;
673
+ constructor(options) {
674
+ super(options);
675
+ const toggleOptions = options?.toggle ?? {};
676
+ const netInfoOptions = options?.netInfo ?? {};
677
+ if (options?.publicApiKey) {
678
+ this._publicApiKey = options.publicApiKey;
679
+ toggleOptions.publicApiKey = options.publicApiKey;
680
+ }
681
+ if (options?.apiKey) {
682
+ this._apiKey = options.apiKey;
683
+ netInfoOptions.apiKey = options.apiKey;
684
+ }
685
+ if (options?.throwErrors !== void 0) {
686
+ toggleOptions.throwErrors = options.throwErrors;
687
+ netInfoOptions.throwErrors = options.throwErrors;
688
+ }
689
+ this._netInfo = new NetInfo(netInfoOptions);
690
+ this._netInfo.on("error", (message, ...args) => this.emit("error", message, ...args));
691
+ this._netInfo.on("info", (message, ...args) => this.emit("info", message, ...args));
692
+ this._netInfo.on("warn", (message, ...args) => this.emit("warn", message, ...args));
693
+ this._toggle = new Toggle(toggleOptions);
694
+ this._toggle.on("error", (message, ...args) => this.emit("error", message, ...args));
695
+ this._toggle.on("info", (message, ...args) => this.emit("info", message, ...args));
696
+ this._toggle.on("warn", (message, ...args) => this.emit("warn", message, ...args));
697
+ }
698
+ get netInfo() {
699
+ return this._netInfo;
700
+ }
701
+ get toggle() {
702
+ return this._toggle;
703
+ }
704
+ get publicApiKey() {
705
+ return this._publicApiKey;
706
+ }
707
+ set publicApiKey(value) {
708
+ this._publicApiKey = value;
709
+ this._toggle.publicApiKey = value;
710
+ }
711
+ get apiKey() {
712
+ return this._apiKey;
713
+ }
714
+ set apiKey(value) {
715
+ this._apiKey = value;
716
+ this._netInfo.apiKey = value;
717
+ }
718
+ get throwErrors() {
719
+ return this._netInfo.throwErrors && this._toggle.throwErrors;
720
+ }
721
+ set throwErrors(value) {
722
+ this._netInfo.throwErrors = value;
723
+ this._toggle.throwErrors = value;
724
+ }
725
+ };
445
726
  // Annotate the CommonJS export names for ESM import in node:
446
727
  0 && (module.exports = {
728
+ Hyphen,
447
729
  Toggle,
448
730
  ToggleHooks,
449
731
  loadEnv
package/dist/index.d.cts CHANGED
@@ -1,5 +1,9 @@
1
- import { Hookified } from 'hookified';
1
+ import { Hookified, HookifiedOptions } from 'hookified';
2
2
  import { EvaluationContext, Client } from '@openfeature/server-sdk';
3
+ import * as axios from 'axios';
4
+ import { AxiosRequestConfig } from 'axios';
5
+ import { Cacheable } from 'cacheable';
6
+ import pino from 'pino';
3
7
 
4
8
  type ToggleContext = EvaluationContext;
5
9
  declare enum ToggleHooks {
@@ -218,4 +222,120 @@ type LoadEnvOptions = {
218
222
  */
219
223
  declare function loadEnv(options?: LoadEnvOptions): void;
220
224
 
221
- export { Toggle, type ToggleCachingOptions, type ToggleContext, type ToggleGetOptions, ToggleHooks, type ToggleOptions, loadEnv };
225
+ type BaseServiceOptions = {
226
+ throwErrors?: boolean;
227
+ } & HookifiedOptions;
228
+ declare class BaseService extends Hookified {
229
+ private _log;
230
+ private _cache;
231
+ private _throwErrors;
232
+ constructor(options?: BaseServiceOptions);
233
+ get log(): pino.Logger;
234
+ set log(value: pino.Logger);
235
+ get cache(): Cacheable;
236
+ set cache(value: Cacheable);
237
+ get throwErrors(): boolean;
238
+ set throwErrors(value: boolean);
239
+ error(message: string, ...args: any[]): void;
240
+ warn(message: string, ...args: any[]): void;
241
+ info(message: string, ...args: any[]): void;
242
+ get<T>(url: string, config?: AxiosRequestConfig): Promise<axios.AxiosResponse<T, any>>;
243
+ post<T>(url: string, data: any, config?: AxiosRequestConfig): Promise<axios.AxiosResponse<T, any>>;
244
+ put<T>(url: string, data: any, config?: AxiosRequestConfig): Promise<axios.AxiosResponse<T, any>>;
245
+ delete<T>(url: string, config?: AxiosRequestConfig): Promise<axios.AxiosResponse<T, any>>;
246
+ patch<T>(url: string, data: any, config?: AxiosRequestConfig): Promise<axios.AxiosResponse<T, any>>;
247
+ createHeaders(apiKey?: string): Record<string, string>;
248
+ }
249
+
250
+ type NetInfoOptions = {
251
+ /**
252
+ * API key for authentication. If this is not provided it will try to use `HYPHEN_API_KEY` environment variable.
253
+ * @type {string} - The API key for authentication. This is not the public API key.
254
+ * @default undefined
255
+ */
256
+ apiKey?: string;
257
+ /**
258
+ * Base URI for the API. If not provided, it will use the default Hyphen API base URI.
259
+ * @type {string} - The base URI for the API.
260
+ * @default 'https://net.info'
261
+ */
262
+ baseUri?: string;
263
+ } & BaseServiceOptions;
264
+ type ipInfo = {
265
+ ip: string;
266
+ type: string;
267
+ location: {
268
+ country: string;
269
+ region: string;
270
+ city: string;
271
+ lat: number;
272
+ lng: number;
273
+ postalCode: string;
274
+ timezone: string;
275
+ geonameId: number;
276
+ };
277
+ };
278
+ type ipInfoError = {
279
+ ip: string;
280
+ type: string;
281
+ errorMessage: string;
282
+ };
283
+ declare class NetInfo extends BaseService {
284
+ private _apiKey;
285
+ private _baseUri;
286
+ constructor(options?: NetInfoOptions);
287
+ /**
288
+ * Gets or sets the API key for authentication.
289
+ * If not set, it will try to use the `HYPHEN_API_KEY` environment variable.
290
+ * @type {string | undefined}
291
+ */
292
+ get apiKey(): string | undefined;
293
+ /**
294
+ * Sets the API key for authentication.
295
+ * @param {string | undefined} value - The API key to set.
296
+ */
297
+ set apiKey(value: string | undefined);
298
+ /**
299
+ * Gets or sets the base URI for the API.
300
+ * @type {string}
301
+ */
302
+ get baseUri(): string;
303
+ /**
304
+ * Sets the base URI for the API.
305
+ * @param {string} value - The base URI to set.
306
+ */
307
+ set baseUri(value: string);
308
+ setApiKey(value: string | undefined): void;
309
+ /**
310
+ * Fetches GeoIP information for a given IP address.
311
+ * @param {string} ip - The IP address to fetch GeoIP information for.
312
+ * @returns {Promise<ipInfo | ipInfoError>} - A promise that resolves to the ip information or an error.
313
+ */
314
+ getIpInfo(ip: string): Promise<ipInfo | ipInfoError>;
315
+ getIpInfos(ips: string[]): Promise<Array<ipInfo | ipInfoError>>;
316
+ }
317
+
318
+ type HyphenOptions = {
319
+ publicApiKey?: string;
320
+ apiKey?: string;
321
+ throwErrors?: boolean;
322
+ toggle?: Omit<ToggleOptions, 'apiKey' | 'publicApiKey' | 'throwErrors'>;
323
+ netInfo?: Omit<NetInfoOptions, 'apiKey' | 'publicApiKey' | 'throwErrors'>;
324
+ } & HookifiedOptions;
325
+ declare class Hyphen extends Hookified {
326
+ private readonly _netInfo;
327
+ private readonly _toggle;
328
+ private _publicApiKey?;
329
+ private _apiKey?;
330
+ constructor(options?: HyphenOptions);
331
+ get netInfo(): NetInfo;
332
+ get toggle(): Toggle;
333
+ get publicApiKey(): string | undefined;
334
+ set publicApiKey(value: string | undefined);
335
+ get apiKey(): string | undefined;
336
+ set apiKey(value: string | undefined);
337
+ get throwErrors(): boolean;
338
+ set throwErrors(value: boolean);
339
+ }
340
+
341
+ export { Hyphen, type HyphenOptions, Toggle, type ToggleCachingOptions, type ToggleContext, type ToggleGetOptions, ToggleHooks, type ToggleOptions, loadEnv };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,9 @@
1
- import { Hookified } from 'hookified';
1
+ import { Hookified, HookifiedOptions } from 'hookified';
2
2
  import { EvaluationContext, Client } from '@openfeature/server-sdk';
3
+ import * as axios from 'axios';
4
+ import { AxiosRequestConfig } from 'axios';
5
+ import { Cacheable } from 'cacheable';
6
+ import pino from 'pino';
3
7
 
4
8
  type ToggleContext = EvaluationContext;
5
9
  declare enum ToggleHooks {
@@ -218,4 +222,120 @@ type LoadEnvOptions = {
218
222
  */
219
223
  declare function loadEnv(options?: LoadEnvOptions): void;
220
224
 
221
- export { Toggle, type ToggleCachingOptions, type ToggleContext, type ToggleGetOptions, ToggleHooks, type ToggleOptions, loadEnv };
225
+ type BaseServiceOptions = {
226
+ throwErrors?: boolean;
227
+ } & HookifiedOptions;
228
+ declare class BaseService extends Hookified {
229
+ private _log;
230
+ private _cache;
231
+ private _throwErrors;
232
+ constructor(options?: BaseServiceOptions);
233
+ get log(): pino.Logger;
234
+ set log(value: pino.Logger);
235
+ get cache(): Cacheable;
236
+ set cache(value: Cacheable);
237
+ get throwErrors(): boolean;
238
+ set throwErrors(value: boolean);
239
+ error(message: string, ...args: any[]): void;
240
+ warn(message: string, ...args: any[]): void;
241
+ info(message: string, ...args: any[]): void;
242
+ get<T>(url: string, config?: AxiosRequestConfig): Promise<axios.AxiosResponse<T, any>>;
243
+ post<T>(url: string, data: any, config?: AxiosRequestConfig): Promise<axios.AxiosResponse<T, any>>;
244
+ put<T>(url: string, data: any, config?: AxiosRequestConfig): Promise<axios.AxiosResponse<T, any>>;
245
+ delete<T>(url: string, config?: AxiosRequestConfig): Promise<axios.AxiosResponse<T, any>>;
246
+ patch<T>(url: string, data: any, config?: AxiosRequestConfig): Promise<axios.AxiosResponse<T, any>>;
247
+ createHeaders(apiKey?: string): Record<string, string>;
248
+ }
249
+
250
+ type NetInfoOptions = {
251
+ /**
252
+ * API key for authentication. If this is not provided it will try to use `HYPHEN_API_KEY` environment variable.
253
+ * @type {string} - The API key for authentication. This is not the public API key.
254
+ * @default undefined
255
+ */
256
+ apiKey?: string;
257
+ /**
258
+ * Base URI for the API. If not provided, it will use the default Hyphen API base URI.
259
+ * @type {string} - The base URI for the API.
260
+ * @default 'https://net.info'
261
+ */
262
+ baseUri?: string;
263
+ } & BaseServiceOptions;
264
+ type ipInfo = {
265
+ ip: string;
266
+ type: string;
267
+ location: {
268
+ country: string;
269
+ region: string;
270
+ city: string;
271
+ lat: number;
272
+ lng: number;
273
+ postalCode: string;
274
+ timezone: string;
275
+ geonameId: number;
276
+ };
277
+ };
278
+ type ipInfoError = {
279
+ ip: string;
280
+ type: string;
281
+ errorMessage: string;
282
+ };
283
+ declare class NetInfo extends BaseService {
284
+ private _apiKey;
285
+ private _baseUri;
286
+ constructor(options?: NetInfoOptions);
287
+ /**
288
+ * Gets or sets the API key for authentication.
289
+ * If not set, it will try to use the `HYPHEN_API_KEY` environment variable.
290
+ * @type {string | undefined}
291
+ */
292
+ get apiKey(): string | undefined;
293
+ /**
294
+ * Sets the API key for authentication.
295
+ * @param {string | undefined} value - The API key to set.
296
+ */
297
+ set apiKey(value: string | undefined);
298
+ /**
299
+ * Gets or sets the base URI for the API.
300
+ * @type {string}
301
+ */
302
+ get baseUri(): string;
303
+ /**
304
+ * Sets the base URI for the API.
305
+ * @param {string} value - The base URI to set.
306
+ */
307
+ set baseUri(value: string);
308
+ setApiKey(value: string | undefined): void;
309
+ /**
310
+ * Fetches GeoIP information for a given IP address.
311
+ * @param {string} ip - The IP address to fetch GeoIP information for.
312
+ * @returns {Promise<ipInfo | ipInfoError>} - A promise that resolves to the ip information or an error.
313
+ */
314
+ getIpInfo(ip: string): Promise<ipInfo | ipInfoError>;
315
+ getIpInfos(ips: string[]): Promise<Array<ipInfo | ipInfoError>>;
316
+ }
317
+
318
+ type HyphenOptions = {
319
+ publicApiKey?: string;
320
+ apiKey?: string;
321
+ throwErrors?: boolean;
322
+ toggle?: Omit<ToggleOptions, 'apiKey' | 'publicApiKey' | 'throwErrors'>;
323
+ netInfo?: Omit<NetInfoOptions, 'apiKey' | 'publicApiKey' | 'throwErrors'>;
324
+ } & HookifiedOptions;
325
+ declare class Hyphen extends Hookified {
326
+ private readonly _netInfo;
327
+ private readonly _toggle;
328
+ private _publicApiKey?;
329
+ private _apiKey?;
330
+ constructor(options?: HyphenOptions);
331
+ get netInfo(): NetInfo;
332
+ get toggle(): Toggle;
333
+ get publicApiKey(): string | undefined;
334
+ set publicApiKey(value: string | undefined);
335
+ get apiKey(): string | undefined;
336
+ set apiKey(value: string | undefined);
337
+ get throwErrors(): boolean;
338
+ set throwErrors(value: boolean);
339
+ }
340
+
341
+ export { Hyphen, type HyphenOptions, Toggle, type ToggleCachingOptions, type ToggleContext, type ToggleGetOptions, ToggleHooks, type ToggleOptions, loadEnv };
package/dist/index.js CHANGED
@@ -406,7 +406,288 @@ function loadEnv(options) {
406
406
  }
407
407
  }
408
408
  __name(loadEnv, "loadEnv");
409
+
410
+ // src/hyphen.ts
411
+ import { Hookified as Hookified3 } from "hookified";
412
+
413
+ // src/net-info.ts
414
+ import process3 from "process";
415
+
416
+ // src/base-service.ts
417
+ import { Hookified as Hookified2 } from "hookified";
418
+ import { Cacheable } from "cacheable";
419
+ import axios from "axios";
420
+ import pino from "pino";
421
+ var ErrorMessages = /* @__PURE__ */ function(ErrorMessages2) {
422
+ ErrorMessages2["API_KEY_REQUIRED"] = "API key is required. Please provide it via options or set the HYPHEN_API_KEY environment variable.";
423
+ ErrorMessages2["PUBLIC_API_KEY_SHOULD_NOT_BE_USED"] = "The provided API key is a public API key. Please provide a valid non public API key for authentication.";
424
+ return ErrorMessages2;
425
+ }({});
426
+ var BaseService = class extends Hookified2 {
427
+ static {
428
+ __name(this, "BaseService");
429
+ }
430
+ _log = pino();
431
+ _cache = new Cacheable();
432
+ _throwErrors = false;
433
+ constructor(options) {
434
+ super(options);
435
+ if (options && options.throwErrors !== void 0) {
436
+ this._throwErrors = options.throwErrors;
437
+ }
438
+ }
439
+ get log() {
440
+ return this._log;
441
+ }
442
+ set log(value) {
443
+ this._log = value;
444
+ }
445
+ get cache() {
446
+ return this._cache;
447
+ }
448
+ set cache(value) {
449
+ this._cache = value;
450
+ }
451
+ get throwErrors() {
452
+ return this._throwErrors;
453
+ }
454
+ set throwErrors(value) {
455
+ this._throwErrors = value;
456
+ }
457
+ error(message, ...args) {
458
+ this._log.error(message, ...args);
459
+ this.emit("error", message, ...args);
460
+ if (this.throwErrors) {
461
+ throw new Error(message);
462
+ }
463
+ }
464
+ warn(message, ...args) {
465
+ this._log.warn(message, ...args);
466
+ this.emit("warn", message, ...args);
467
+ }
468
+ info(message, ...args) {
469
+ this._log.info(message, ...args);
470
+ this.emit("info", message, ...args);
471
+ }
472
+ async get(url, config2) {
473
+ return axios.get(url, config2);
474
+ }
475
+ async post(url, data, config2) {
476
+ return axios.post(url, data, config2);
477
+ }
478
+ async put(url, data, config2) {
479
+ return axios.put(url, data, config2);
480
+ }
481
+ async delete(url, config2) {
482
+ return axios.delete(url, config2);
483
+ }
484
+ async patch(url, data, config2) {
485
+ return axios.patch(url, data, config2);
486
+ }
487
+ createHeaders(apiKey) {
488
+ const headers = {
489
+ "content-type": "application/json",
490
+ accept: "application/json"
491
+ };
492
+ if (apiKey) {
493
+ headers["x-api-key"] = apiKey;
494
+ }
495
+ return headers;
496
+ }
497
+ };
498
+
499
+ // src/net-info.ts
500
+ loadEnv();
501
+ var NetInfo = class extends BaseService {
502
+ static {
503
+ __name(this, "NetInfo");
504
+ }
505
+ _apiKey;
506
+ _baseUri = "https://net.info";
507
+ constructor(options) {
508
+ super(options);
509
+ if (options?.baseUri) {
510
+ this._baseUri = options.baseUri;
511
+ }
512
+ this.setApiKey(options?.apiKey);
513
+ if (!this._apiKey && process3.env.HYPHEN_API_KEY) {
514
+ this.setApiKey(process3.env.HYPHEN_API_KEY);
515
+ }
516
+ if (!this._apiKey) {
517
+ this.error(ErrorMessages.API_KEY_REQUIRED);
518
+ }
519
+ }
520
+ /**
521
+ * Gets or sets the API key for authentication.
522
+ * If not set, it will try to use the `HYPHEN_API_KEY` environment variable.
523
+ * @type {string | undefined}
524
+ */
525
+ get apiKey() {
526
+ return this._apiKey;
527
+ }
528
+ /**
529
+ * Sets the API key for authentication.
530
+ * @param {string | undefined} value - The API key to set.
531
+ */
532
+ set apiKey(value) {
533
+ this.setApiKey(value);
534
+ }
535
+ /**
536
+ * Gets or sets the base URI for the API.
537
+ * @type {string}
538
+ */
539
+ get baseUri() {
540
+ return this._baseUri;
541
+ }
542
+ /**
543
+ * Sets the base URI for the API.
544
+ * @param {string} value - The base URI to set.
545
+ */
546
+ set baseUri(value) {
547
+ this._baseUri = value;
548
+ }
549
+ setApiKey(value) {
550
+ if (value?.startsWith("public_")) {
551
+ this.error(ErrorMessages.PUBLIC_API_KEY_SHOULD_NOT_BE_USED);
552
+ return;
553
+ }
554
+ this._apiKey = value;
555
+ }
556
+ /**
557
+ * Fetches GeoIP information for a given IP address.
558
+ * @param {string} ip - The IP address to fetch GeoIP information for.
559
+ * @returns {Promise<ipInfo | ipInfoError>} - A promise that resolves to the ip information or an error.
560
+ */
561
+ async getIpInfo(ip) {
562
+ try {
563
+ if (!this._apiKey) {
564
+ throw new Error(ErrorMessages.API_KEY_REQUIRED);
565
+ }
566
+ const url = `${this._baseUri}/ip/${ip}`;
567
+ const headers = this.createHeaders(this._apiKey);
568
+ const response = await this.get(url, {
569
+ headers
570
+ });
571
+ if (response.status !== 200) {
572
+ const errorResult = {
573
+ ip,
574
+ type: "error",
575
+ errorMessage: `Failed to fetch ip info: ${response.statusText}`
576
+ };
577
+ return errorResult;
578
+ }
579
+ return response.data;
580
+ } catch (error) {
581
+ this.error(`Failed to fetch ip info: ${error instanceof Error ? error.message : "Unknown error"}`);
582
+ const errorResult = {
583
+ ip,
584
+ type: "error",
585
+ errorMessage: error instanceof Error ? error.message : "Unknown error"
586
+ };
587
+ return errorResult;
588
+ }
589
+ }
590
+ async getIpInfos(ips) {
591
+ if (!Array.isArray(ips) || ips.length === 0) {
592
+ this.error("The provided IPs array is invalid. It should be a non-empty array of strings.");
593
+ return [];
594
+ }
595
+ const errorResults = [];
596
+ try {
597
+ if (!this._apiKey) {
598
+ throw new Error(ErrorMessages.API_KEY_REQUIRED);
599
+ }
600
+ const url = `${this._baseUri}/ip`;
601
+ const headers = this.createHeaders(this._apiKey);
602
+ const response = await this.post(url, ips, {
603
+ headers
604
+ });
605
+ if (response.status !== 200) {
606
+ errorResults.push({
607
+ ip: "",
608
+ type: "error",
609
+ errorMessage: `Failed to fetch ip infos: ${response.statusText}`
610
+ });
611
+ return errorResults;
612
+ }
613
+ const responseData = response?.data;
614
+ return responseData.data;
615
+ } catch (error) {
616
+ this.error(`Failed to fetch ip infos: ${error instanceof Error ? error.message : "Unknown error"}`);
617
+ errorResults.push({
618
+ ip: "",
619
+ type: "error",
620
+ errorMessage: error instanceof Error ? error.message : "Unknown error"
621
+ });
622
+ return errorResults;
623
+ }
624
+ }
625
+ };
626
+
627
+ // src/hyphen.ts
628
+ var Hyphen = class extends Hookified3 {
629
+ static {
630
+ __name(this, "Hyphen");
631
+ }
632
+ _netInfo;
633
+ _toggle;
634
+ _publicApiKey;
635
+ _apiKey;
636
+ constructor(options) {
637
+ super(options);
638
+ const toggleOptions = options?.toggle ?? {};
639
+ const netInfoOptions = options?.netInfo ?? {};
640
+ if (options?.publicApiKey) {
641
+ this._publicApiKey = options.publicApiKey;
642
+ toggleOptions.publicApiKey = options.publicApiKey;
643
+ }
644
+ if (options?.apiKey) {
645
+ this._apiKey = options.apiKey;
646
+ netInfoOptions.apiKey = options.apiKey;
647
+ }
648
+ if (options?.throwErrors !== void 0) {
649
+ toggleOptions.throwErrors = options.throwErrors;
650
+ netInfoOptions.throwErrors = options.throwErrors;
651
+ }
652
+ this._netInfo = new NetInfo(netInfoOptions);
653
+ this._netInfo.on("error", (message, ...args) => this.emit("error", message, ...args));
654
+ this._netInfo.on("info", (message, ...args) => this.emit("info", message, ...args));
655
+ this._netInfo.on("warn", (message, ...args) => this.emit("warn", message, ...args));
656
+ this._toggle = new Toggle(toggleOptions);
657
+ this._toggle.on("error", (message, ...args) => this.emit("error", message, ...args));
658
+ this._toggle.on("info", (message, ...args) => this.emit("info", message, ...args));
659
+ this._toggle.on("warn", (message, ...args) => this.emit("warn", message, ...args));
660
+ }
661
+ get netInfo() {
662
+ return this._netInfo;
663
+ }
664
+ get toggle() {
665
+ return this._toggle;
666
+ }
667
+ get publicApiKey() {
668
+ return this._publicApiKey;
669
+ }
670
+ set publicApiKey(value) {
671
+ this._publicApiKey = value;
672
+ this._toggle.publicApiKey = value;
673
+ }
674
+ get apiKey() {
675
+ return this._apiKey;
676
+ }
677
+ set apiKey(value) {
678
+ this._apiKey = value;
679
+ this._netInfo.apiKey = value;
680
+ }
681
+ get throwErrors() {
682
+ return this._netInfo.throwErrors && this._toggle.throwErrors;
683
+ }
684
+ set throwErrors(value) {
685
+ this._netInfo.throwErrors = value;
686
+ this._toggle.throwErrors = value;
687
+ }
688
+ };
409
689
  export {
690
+ Hyphen,
410
691
  Toggle,
411
692
  ToggleHooks,
412
693
  loadEnv
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyphen/sdk",
3
- "version": "1.7.0",
3
+ "version": "1.9.0",
4
4
  "description": "Hyphen SDK for Node.js",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
@@ -29,15 +29,15 @@
29
29
  "author": "Team Hyphen <hello@hyphen.ai>",
30
30
  "license": "MIT",
31
31
  "devDependencies": {
32
- "@swc/core": "^1.11.29",
33
- "@types/node": "^22.15.21",
34
- "@vitest/coverage-v8": "^3.1.4",
32
+ "@swc/core": "^1.12.9",
33
+ "@types/node": "^24.0.10",
34
+ "@vitest/coverage-v8": "^3.2.4",
35
35
  "rimraf": "^6.0.1",
36
36
  "tsd": "^0.32.0",
37
37
  "tsup": "^8.5.0",
38
38
  "typescript": "^5.8.3",
39
- "vitest": "^3.1.4",
40
- "xo": "^1.1.0"
39
+ "vitest": "^3.2.4",
40
+ "xo": "^1.1.1"
41
41
  },
42
42
  "files": [
43
43
  "dist",
@@ -47,9 +47,9 @@
47
47
  "@hyphen/openfeature-server-provider": "^1.0.7",
48
48
  "@openfeature/server-sdk": "^1.18.0",
49
49
  "axios": "^1.10.0",
50
- "cacheable": "^1.10.0",
51
- "dotenv": "^16.5.0",
52
- "hookified": "^1.9.0",
50
+ "cacheable": "^1.10.1",
51
+ "dotenv": "^17.0.1",
52
+ "hookified": "^1.10.0",
53
53
  "pino": "^9.7.0"
54
54
  }
55
55
  }