@hyphen/sdk 2.0.1 → 2.2.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
@@ -24,6 +24,7 @@ The Hyphen Node.js SDK is a JavaScript library that allows developers to easily
24
24
  - [ENV - Secret Management Service](#env---secret-management-service)
25
25
  - [Loading Environment Variables](#loading-environment-variables)
26
26
  - [Net Info - Geo Information Service](#net-info---geo-information-service)
27
+ - [Execution Context - API Key Validation](#execution-context---api-key-validation)
27
28
  - [Link - Short Code Service](#link---short-code-service)
28
29
  - [Creating a Short Code](#creating-a-short-code)
29
30
  - [Updating a Short Code](#updating-a-short-code)
@@ -673,6 +674,75 @@ console.log('IP Infos:', ipInfos);
673
674
 
674
675
  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.
675
676
 
677
+ # Execution Context - API Key Validation
678
+
679
+ The `getExecutionContext` function validates an API key and returns information about the authenticated user, organization, and request context. This is useful for verifying API keys and getting user/organization details.
680
+
681
+ ## Basic Usage
682
+
683
+ ```javascript
684
+ import { getExecutionContext } from '@hyphen/sdk';
685
+
686
+ const context = await getExecutionContext('your-api-key');
687
+
688
+ console.log('User:', context.user?.name);
689
+ console.log('Organization:', context.member?.organization?.name);
690
+ console.log('IP Address:', context.ipAddress);
691
+ console.log('Location:', context.location?.city, context.location?.country);
692
+ ```
693
+
694
+ ## Options
695
+
696
+ | Option | Type | Description |
697
+ |--------|------|-------------|
698
+ | `organizationId` | `string` | Optional organization ID to scope the context request |
699
+ | `baseUri` | `string` | Custom API base URI (defaults to `https://api.hyphen.ai`) |
700
+ | `cache` | `Cacheable` | Cacheable instance for caching requests |
701
+
702
+ ## With Organization ID
703
+
704
+ If you need to get context for a specific organization:
705
+
706
+ ```javascript
707
+ import { getExecutionContext } from '@hyphen/sdk';
708
+
709
+ const context = await getExecutionContext('your-api-key', {
710
+ organizationId: 'org_123456789',
711
+ });
712
+
713
+ console.log('Organization:', context.organization?.name);
714
+ ```
715
+
716
+ ## With Caching
717
+
718
+ To enable caching of execution context requests:
719
+
720
+ ```javascript
721
+ import { Cacheable } from 'cacheable';
722
+ import { getExecutionContext } from '@hyphen/sdk';
723
+
724
+ const cache = new Cacheable({ ttl: 60000 }); // Cache for 60 seconds
725
+
726
+ const context = await getExecutionContext('your-api-key', {
727
+ cache,
728
+ });
729
+
730
+ console.log('User:', context.user?.name);
731
+ ```
732
+
733
+ ## Return Type
734
+
735
+ The function returns an `ExecutionContext` object with the following properties:
736
+
737
+ | Property | Type | Description |
738
+ |----------|------|-------------|
739
+ | `request` | `object` | Request metadata (`id`, `causationId`, `correlationId`) |
740
+ | `user` | `object` | User info (`id`, `name`, `rules`, `type`) |
741
+ | `member` | `object` | Member info with nested `organization` |
742
+ | `organization` | `object` | Organization info (`id`, `name`) |
743
+ | `ipAddress` | `string` | The IP address of the request |
744
+ | `location` | `object` | Geo location (`country`, `region`, `city`, `lat`, `lng`, `postalCode`, `timezone`) |
745
+
676
746
  # Link - Short Code Service
677
747
 
678
748
  The Hyphen Node.js SDK also provides a `Link` class that allows you to create and manage short codes. This can be useful for generating short links for your application.
package/dist/index.cjs CHANGED
@@ -34,6 +34,7 @@ __export(index_exports, {
34
34
  Hyphen: () => Hyphen,
35
35
  Toggle: () => Toggle,
36
36
  env: () => env,
37
+ getExecutionContext: () => getExecutionContext,
37
38
  loadEnv: () => loadEnv
38
39
  });
39
40
  module.exports = __toCommonJS(index_exports);
@@ -42,56 +43,72 @@ module.exports = __toCommonJS(index_exports);
42
43
  var import_node_fs = __toESM(require("fs"), 1);
43
44
  var import_node_path = __toESM(require("path"), 1);
44
45
  var import_node_process = __toESM(require("process"), 1);
45
- var import_dotenv = require("dotenv");
46
+ var import_node_util = require("util");
47
+ function loadEnvFile(filePath, override) {
48
+ try {
49
+ const content = import_node_fs.default.readFileSync(filePath, "utf8");
50
+ const parsed = (0, import_node_util.parseEnv)(content);
51
+ for (const [key, value] of Object.entries(parsed)) {
52
+ if (override || import_node_process.default.env[key] === void 0) {
53
+ import_node_process.default.env[key] = value;
54
+ }
55
+ }
56
+ } catch {
57
+ }
58
+ }
59
+ __name(loadEnvFile, "loadEnvFile");
46
60
  function env(options) {
47
61
  const local = options?.local ?? true;
48
62
  const currentWorkingDirectory = options?.path ?? import_node_process.default.cwd();
49
63
  const envPath = import_node_path.default.resolve(currentWorkingDirectory, ".env");
50
- if (import_node_fs.default.existsSync(envPath)) {
51
- (0, import_dotenv.config)({
52
- path: envPath,
53
- quiet: true,
54
- debug: false
55
- });
56
- }
64
+ loadEnvFile(envPath, false);
57
65
  if (local) {
58
66
  const localEnvPath = import_node_path.default.resolve(currentWorkingDirectory, ".env.local");
59
- if (import_node_fs.default.existsSync(localEnvPath)) {
60
- (0, import_dotenv.config)({
61
- path: localEnvPath,
62
- override: true,
63
- quiet: true,
64
- debug: false
65
- });
66
- }
67
+ loadEnvFile(localEnvPath, true);
67
68
  }
68
69
  const environment = options?.environment ?? import_node_process.default.env.NODE_ENV;
69
70
  if (environment) {
70
71
  const envSpecificPath = import_node_path.default.resolve(currentWorkingDirectory, `.env.${environment}`);
71
- if (import_node_fs.default.existsSync(envSpecificPath)) {
72
- (0, import_dotenv.config)({
73
- path: envSpecificPath,
74
- override: true,
75
- quiet: true,
76
- debug: false
77
- });
78
- }
72
+ loadEnvFile(envSpecificPath, true);
79
73
  if (local) {
80
74
  const envLocalPath = import_node_path.default.resolve(currentWorkingDirectory, `.env.${environment}.local`);
81
- if (import_node_fs.default.existsSync(envLocalPath)) {
82
- (0, import_dotenv.config)({
83
- path: envLocalPath,
84
- override: true,
85
- quiet: true,
86
- debug: false
87
- });
88
- }
75
+ loadEnvFile(envLocalPath, true);
89
76
  }
90
77
  }
91
78
  }
92
79
  __name(env, "env");
93
80
  var loadEnv = env;
94
81
 
82
+ // src/execution-context.ts
83
+ var import_net = require("@cacheable/net");
84
+ async function getExecutionContext(apiKey, options) {
85
+ if (!apiKey) {
86
+ throw new Error("API key is required");
87
+ }
88
+ const baseUri = options?.baseUri ?? "https://api.hyphen.ai";
89
+ let url = `${baseUri}/api/execution-context`;
90
+ if (options?.organizationId) {
91
+ url += `?organizationId=${encodeURIComponent(options.organizationId)}`;
92
+ }
93
+ const net = options?.cache ? new import_net.CacheableNet({
94
+ cache: options.cache
95
+ }) : new import_net.CacheableNet();
96
+ const caching = options?.cache !== void 0;
97
+ const response = await net.get(url, {
98
+ headers: {
99
+ "x-api-key": apiKey,
100
+ "content-type": "application/json",
101
+ accept: "application/json"
102
+ },
103
+ caching
104
+ });
105
+ if (response.response.status !== 200) {
106
+ throw new Error(`Failed to get execution context: ${response.response.statusText}`);
107
+ }
108
+ return response.data;
109
+ }
110
+ __name(getExecutionContext, "getExecutionContext");
111
+
95
112
  // src/hyphen.ts
96
113
  var import_hookified3 = require("hookified");
97
114
 
@@ -100,7 +117,7 @@ var import_node_buffer = require("buffer");
100
117
  var import_node_process2 = __toESM(require("process"), 1);
101
118
 
102
119
  // src/base-service.ts
103
- var import_net = require("@cacheable/net");
120
+ var import_net2 = require("@cacheable/net");
104
121
  var import_cacheable = require("cacheable");
105
122
  var import_hookified = require("hookified");
106
123
  var import_pino = __toESM(require("pino"), 1);
@@ -122,7 +139,7 @@ var BaseService = class extends import_hookified.Hookified {
122
139
  if (options && options.throwErrors !== void 0) {
123
140
  this._throwErrors = options.throwErrors;
124
141
  }
125
- this._net = new import_net.CacheableNet({
142
+ this._net = new import_net2.CacheableNet({
126
143
  cache: this._cache
127
144
  });
128
145
  }
@@ -160,59 +177,56 @@ var BaseService = class extends import_hookified.Hookified {
160
177
  this._log.info(message, ...args);
161
178
  this.emit("info", message, ...args);
162
179
  }
163
- async get(url, config2) {
180
+ async get(url, config) {
164
181
  let finalUrl = url;
165
- if (config2?.params) {
166
- const params = new URLSearchParams(config2.params);
182
+ if (config?.params) {
183
+ const params = new URLSearchParams(config.params);
167
184
  finalUrl = `${url}?${params.toString()}`;
168
185
  }
169
- const { params: _, ...fetchConfig } = config2 || {};
186
+ const { params: _, ...fetchConfig } = config || {};
170
187
  const response = await this._net.get(finalUrl, fetchConfig);
171
188
  return {
172
189
  data: response.data,
173
190
  status: response.response.status,
174
191
  statusText: response.response.statusText,
175
192
  headers: response.response.headers,
176
- config: config2,
193
+ config,
177
194
  request: void 0
178
195
  };
179
196
  }
180
- async post(url, data, config2) {
181
- const response = await this._net.post(url, data, config2);
197
+ async post(url, data, config) {
198
+ const response = await this._net.post(url, data, config);
182
199
  return {
183
200
  data: response.data,
184
201
  status: response.response.status,
185
202
  statusText: response.response.statusText,
186
203
  headers: response.response.headers,
187
- config: config2,
204
+ config,
188
205
  request: void 0
189
206
  };
190
207
  }
191
- async put(url, data, config2) {
192
- const response = await this._net.put(url, data, config2);
208
+ async put(url, data, config) {
209
+ const response = await this._net.put(url, data, config);
193
210
  return {
194
211
  data: response.data,
195
212
  status: response.response.status,
196
213
  statusText: response.response.statusText,
197
214
  headers: response.response.headers,
198
- config: config2,
215
+ config,
199
216
  request: void 0
200
217
  };
201
218
  }
202
- async delete(url, config2) {
219
+ async delete(url, config) {
203
220
  const headers = {
204
- ...config2?.headers
221
+ ...config?.headers
205
222
  };
206
223
  if (headers) {
207
224
  delete headers["content-type"];
208
225
  }
209
- const { data: configData, ...restConfig } = config2 || {};
226
+ const { data: configData, ...restConfig } = config || {};
210
227
  let body;
211
228
  if (configData) {
212
- body = typeof configData === "string" ? (
213
- /* c8 ignore next */
214
- configData
215
- ) : JSON.stringify(configData);
229
+ body = typeof configData === "string" ? configData : JSON.stringify(configData);
216
230
  if (!headers["content-type"] && !headers["Content-Type"]) {
217
231
  headers["content-type"] = "application/json";
218
232
  }
@@ -237,18 +251,18 @@ var BaseService = class extends import_hookified.Hookified {
237
251
  status: response.status,
238
252
  statusText: response.statusText,
239
253
  headers: response.headers,
240
- config: config2,
254
+ config,
241
255
  request: void 0
242
256
  };
243
257
  }
244
- async patch(url, data, config2) {
245
- const response = await this._net.patch(url, data, config2);
258
+ async patch(url, data, config) {
259
+ const response = await this._net.patch(url, data, config);
246
260
  return {
247
261
  data: response.data,
248
262
  status: response.response.status,
249
263
  statusText: response.response.statusText,
250
264
  headers: response.response.headers,
251
- config: config2,
265
+ config,
252
266
  request: void 0
253
267
  };
254
268
  }
@@ -725,6 +739,7 @@ var NetInfo = class extends BaseService {
725
739
  const errorResult = {
726
740
  ip,
727
741
  type: "error",
742
+ /* v8 ignore next -- @preserve */
728
743
  errorMessage: error instanceof Error ? error.message : "Unknown error"
729
744
  };
730
745
  return errorResult;
@@ -768,7 +783,7 @@ var NetInfo = class extends BaseService {
768
783
  };
769
784
 
770
785
  // src/toggle.ts
771
- var import_net2 = require("@cacheable/net");
786
+ var import_net3 = require("@cacheable/net");
772
787
  var import_hookified2 = require("hookified");
773
788
  var Toggle = class extends import_hookified2.Hookified {
774
789
  static {
@@ -779,7 +794,7 @@ var Toggle = class extends import_hookified2.Hookified {
779
794
  _applicationId;
780
795
  _environment;
781
796
  _horizonUrls = [];
782
- _net = new import_net2.CacheableNet();
797
+ _net = new import_net3.CacheableNet();
783
798
  _defaultContext;
784
799
  _defaultTargetingKey = `${Math.random().toString(36).substring(7)}`;
785
800
  /**
@@ -1011,6 +1026,7 @@ var Toggle = class extends import_hookified2.Hookified {
1011
1026
  try {
1012
1027
  const context = {
1013
1028
  application: this._applicationId ?? "",
1029
+ /* v8 ignore next -- @preserve */
1014
1030
  environment: this._environment ?? "development"
1015
1031
  };
1016
1032
  if (options?.context) {
@@ -1201,7 +1217,10 @@ var Toggle = class extends import_hookified2.Hookified {
1201
1217
  const data = await response.json();
1202
1218
  return data;
1203
1219
  } catch (error) {
1204
- const fetchError = error instanceof Error ? error : new Error("Unknown fetch error");
1220
+ const fetchError = (
1221
+ /* v8 ignore next -- @preserve */
1222
+ error instanceof Error ? error : new Error("Unknown fetch error")
1223
+ );
1205
1224
  const statusMatch = fetchError.message.match(/status (\d{3})/);
1206
1225
  if (statusMatch) {
1207
1226
  const status = statusMatch[1];
@@ -1470,5 +1489,7 @@ var Hyphen = class extends import_hookified3.Hookified {
1470
1489
  Hyphen,
1471
1490
  Toggle,
1472
1491
  env,
1492
+ getExecutionContext,
1473
1493
  loadEnv
1474
1494
  });
1495
+ /* v8 ignore next -- @preserve */
package/dist/index.d.cts CHANGED
@@ -1,6 +1,6 @@
1
+ import { Cacheable } from 'cacheable';
1
2
  import { HookifiedOptions, Hookified } from 'hookified';
2
3
  import { FetchRequestInit, FetchOptions } from '@cacheable/net';
3
- import { Cacheable } from 'cacheable';
4
4
  import pino from 'pino';
5
5
 
6
6
  type EnvOptions = {
@@ -21,6 +21,84 @@ declare function env(options?: EnvOptions): void;
21
21
  declare const loadEnv: typeof env;
22
22
  type LoadEnvOptions = EnvOptions;
23
23
 
24
+ type ExecutionContextOptions = {
25
+ /**
26
+ * The organization ID for the Hyphen API.
27
+ */
28
+ organizationId?: string;
29
+ /**
30
+ * The base URI for the Hyphen API.
31
+ * @default "https://api.hyphen.ai"
32
+ */
33
+ baseUri?: string;
34
+ /**
35
+ * The Cacheable instance to use for caching requests.
36
+ */
37
+ cache?: Cacheable;
38
+ };
39
+ type ExecutionContextRequest = {
40
+ id?: string;
41
+ causationId?: string;
42
+ correlationId?: string;
43
+ };
44
+ type ExecutionContextUser = {
45
+ id?: string;
46
+ name?: string;
47
+ rules?: Record<string, unknown>[];
48
+ type?: string;
49
+ };
50
+ type ExecutionContextMember = {
51
+ id?: string;
52
+ name?: string;
53
+ organization?: {
54
+ id?: string;
55
+ name?: string;
56
+ };
57
+ rules?: Record<string, unknown>[];
58
+ };
59
+ type ExecutionContextOrganization = {
60
+ id?: string;
61
+ name?: string;
62
+ };
63
+ type ExecutionContextLocation = {
64
+ country?: string;
65
+ region?: string;
66
+ city?: string;
67
+ lat?: number;
68
+ lng?: number;
69
+ postalCode?: string;
70
+ timezone?: string;
71
+ };
72
+ type ExecutionContext = {
73
+ request?: ExecutionContextRequest;
74
+ user?: ExecutionContextUser;
75
+ member?: ExecutionContextMember;
76
+ organization?: ExecutionContextOrganization;
77
+ ipAddress?: string;
78
+ location?: ExecutionContextLocation;
79
+ };
80
+ /**
81
+ * Get the execution context for the provided API key.
82
+ * This validates the API key and returns information about the organization, user, and request context.
83
+ *
84
+ * @param apiKey - The API key for the Hyphen API.
85
+ * @param options - Additional options for the request.
86
+ * @returns The execution context.
87
+ * @throws Error if the API key is not provided or if the request fails.
88
+ *
89
+ * @example
90
+ * ```typescript
91
+ * import { getExecutionContext } from '@hyphen/sdk';
92
+ *
93
+ * const context = await getExecutionContext('your-api-key', {
94
+ * organizationId: 'optional-org-id',
95
+ * });
96
+ *
97
+ * console.log(context.organization?.name);
98
+ * ```
99
+ */
100
+ declare function getExecutionContext(apiKey: string, options?: ExecutionContextOptions): Promise<ExecutionContext>;
101
+
24
102
  interface HttpResponse<T = any> {
25
103
  data: T;
26
104
  status: number;
@@ -1049,4 +1127,4 @@ declare class Hyphen extends Hookified {
1049
1127
  set apiKey(value: string | undefined);
1050
1128
  }
1051
1129
 
1052
- export { type EnvOptions, Hyphen, type HyphenOptions, type LoadEnvOptions, Toggle, type ToggleContext, type ToggleEvaluation, ToggleEvents, type ToggleOptions, type ToggleUser, env, loadEnv };
1130
+ export { type EnvOptions, type ExecutionContext, type ExecutionContextLocation, type ExecutionContextMember, type ExecutionContextOptions, type ExecutionContextOrganization, type ExecutionContextRequest, type ExecutionContextUser, Hyphen, type HyphenOptions, type LoadEnvOptions, Toggle, type ToggleContext, type ToggleEvaluation, ToggleEvents, type ToggleOptions, type ToggleUser, env, getExecutionContext, loadEnv };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
+ import { Cacheable } from 'cacheable';
1
2
  import { HookifiedOptions, Hookified } from 'hookified';
2
3
  import { FetchRequestInit, FetchOptions } from '@cacheable/net';
3
- import { Cacheable } from 'cacheable';
4
4
  import pino from 'pino';
5
5
 
6
6
  type EnvOptions = {
@@ -21,6 +21,84 @@ declare function env(options?: EnvOptions): void;
21
21
  declare const loadEnv: typeof env;
22
22
  type LoadEnvOptions = EnvOptions;
23
23
 
24
+ type ExecutionContextOptions = {
25
+ /**
26
+ * The organization ID for the Hyphen API.
27
+ */
28
+ organizationId?: string;
29
+ /**
30
+ * The base URI for the Hyphen API.
31
+ * @default "https://api.hyphen.ai"
32
+ */
33
+ baseUri?: string;
34
+ /**
35
+ * The Cacheable instance to use for caching requests.
36
+ */
37
+ cache?: Cacheable;
38
+ };
39
+ type ExecutionContextRequest = {
40
+ id?: string;
41
+ causationId?: string;
42
+ correlationId?: string;
43
+ };
44
+ type ExecutionContextUser = {
45
+ id?: string;
46
+ name?: string;
47
+ rules?: Record<string, unknown>[];
48
+ type?: string;
49
+ };
50
+ type ExecutionContextMember = {
51
+ id?: string;
52
+ name?: string;
53
+ organization?: {
54
+ id?: string;
55
+ name?: string;
56
+ };
57
+ rules?: Record<string, unknown>[];
58
+ };
59
+ type ExecutionContextOrganization = {
60
+ id?: string;
61
+ name?: string;
62
+ };
63
+ type ExecutionContextLocation = {
64
+ country?: string;
65
+ region?: string;
66
+ city?: string;
67
+ lat?: number;
68
+ lng?: number;
69
+ postalCode?: string;
70
+ timezone?: string;
71
+ };
72
+ type ExecutionContext = {
73
+ request?: ExecutionContextRequest;
74
+ user?: ExecutionContextUser;
75
+ member?: ExecutionContextMember;
76
+ organization?: ExecutionContextOrganization;
77
+ ipAddress?: string;
78
+ location?: ExecutionContextLocation;
79
+ };
80
+ /**
81
+ * Get the execution context for the provided API key.
82
+ * This validates the API key and returns information about the organization, user, and request context.
83
+ *
84
+ * @param apiKey - The API key for the Hyphen API.
85
+ * @param options - Additional options for the request.
86
+ * @returns The execution context.
87
+ * @throws Error if the API key is not provided or if the request fails.
88
+ *
89
+ * @example
90
+ * ```typescript
91
+ * import { getExecutionContext } from '@hyphen/sdk';
92
+ *
93
+ * const context = await getExecutionContext('your-api-key', {
94
+ * organizationId: 'optional-org-id',
95
+ * });
96
+ *
97
+ * console.log(context.organization?.name);
98
+ * ```
99
+ */
100
+ declare function getExecutionContext(apiKey: string, options?: ExecutionContextOptions): Promise<ExecutionContext>;
101
+
24
102
  interface HttpResponse<T = any> {
25
103
  data: T;
26
104
  status: number;
@@ -1049,4 +1127,4 @@ declare class Hyphen extends Hookified {
1049
1127
  set apiKey(value: string | undefined);
1050
1128
  }
1051
1129
 
1052
- export { type EnvOptions, Hyphen, type HyphenOptions, type LoadEnvOptions, Toggle, type ToggleContext, type ToggleEvaluation, ToggleEvents, type ToggleOptions, type ToggleUser, env, loadEnv };
1130
+ export { type EnvOptions, type ExecutionContext, type ExecutionContextLocation, type ExecutionContextMember, type ExecutionContextOptions, type ExecutionContextOrganization, type ExecutionContextRequest, type ExecutionContextUser, Hyphen, type HyphenOptions, type LoadEnvOptions, Toggle, type ToggleContext, type ToggleEvaluation, ToggleEvents, type ToggleOptions, type ToggleUser, env, getExecutionContext, loadEnv };
package/dist/index.js CHANGED
@@ -5,56 +5,72 @@ var __name = (target, value) => __defProp(target, "name", { value, configurable:
5
5
  import fs from "fs";
6
6
  import path from "path";
7
7
  import process from "process";
8
- import { config } from "dotenv";
8
+ import { parseEnv } from "util";
9
+ function loadEnvFile(filePath, override) {
10
+ try {
11
+ const content = fs.readFileSync(filePath, "utf8");
12
+ const parsed = parseEnv(content);
13
+ for (const [key, value] of Object.entries(parsed)) {
14
+ if (override || process.env[key] === void 0) {
15
+ process.env[key] = value;
16
+ }
17
+ }
18
+ } catch {
19
+ }
20
+ }
21
+ __name(loadEnvFile, "loadEnvFile");
9
22
  function env(options) {
10
23
  const local = options?.local ?? true;
11
24
  const currentWorkingDirectory = options?.path ?? process.cwd();
12
25
  const envPath = path.resolve(currentWorkingDirectory, ".env");
13
- if (fs.existsSync(envPath)) {
14
- config({
15
- path: envPath,
16
- quiet: true,
17
- debug: false
18
- });
19
- }
26
+ loadEnvFile(envPath, false);
20
27
  if (local) {
21
28
  const localEnvPath = path.resolve(currentWorkingDirectory, ".env.local");
22
- if (fs.existsSync(localEnvPath)) {
23
- config({
24
- path: localEnvPath,
25
- override: true,
26
- quiet: true,
27
- debug: false
28
- });
29
- }
29
+ loadEnvFile(localEnvPath, true);
30
30
  }
31
31
  const environment = options?.environment ?? process.env.NODE_ENV;
32
32
  if (environment) {
33
33
  const envSpecificPath = path.resolve(currentWorkingDirectory, `.env.${environment}`);
34
- if (fs.existsSync(envSpecificPath)) {
35
- config({
36
- path: envSpecificPath,
37
- override: true,
38
- quiet: true,
39
- debug: false
40
- });
41
- }
34
+ loadEnvFile(envSpecificPath, true);
42
35
  if (local) {
43
36
  const envLocalPath = path.resolve(currentWorkingDirectory, `.env.${environment}.local`);
44
- if (fs.existsSync(envLocalPath)) {
45
- config({
46
- path: envLocalPath,
47
- override: true,
48
- quiet: true,
49
- debug: false
50
- });
51
- }
37
+ loadEnvFile(envLocalPath, true);
52
38
  }
53
39
  }
54
40
  }
55
41
  __name(env, "env");
56
42
  var loadEnv = env;
57
43
 
44
+ // src/execution-context.ts
45
+ import { CacheableNet } from "@cacheable/net";
46
+ async function getExecutionContext(apiKey, options) {
47
+ if (!apiKey) {
48
+ throw new Error("API key is required");
49
+ }
50
+ const baseUri = options?.baseUri ?? "https://api.hyphen.ai";
51
+ let url = `${baseUri}/api/execution-context`;
52
+ if (options?.organizationId) {
53
+ url += `?organizationId=${encodeURIComponent(options.organizationId)}`;
54
+ }
55
+ const net = options?.cache ? new CacheableNet({
56
+ cache: options.cache
57
+ }) : new CacheableNet();
58
+ const caching = options?.cache !== void 0;
59
+ const response = await net.get(url, {
60
+ headers: {
61
+ "x-api-key": apiKey,
62
+ "content-type": "application/json",
63
+ accept: "application/json"
64
+ },
65
+ caching
66
+ });
67
+ if (response.response.status !== 200) {
68
+ throw new Error(`Failed to get execution context: ${response.response.statusText}`);
69
+ }
70
+ return response.data;
71
+ }
72
+ __name(getExecutionContext, "getExecutionContext");
73
+
58
74
  // src/hyphen.ts
59
75
  import { Hookified as Hookified3 } from "hookified";
60
76
 
@@ -63,7 +79,7 @@ import { Buffer as Buffer2 } from "buffer";
63
79
  import process2 from "process";
64
80
 
65
81
  // src/base-service.ts
66
- import { CacheableNet } from "@cacheable/net";
82
+ import { CacheableNet as CacheableNet2 } from "@cacheable/net";
67
83
  import { Cacheable } from "cacheable";
68
84
  import { Hookified } from "hookified";
69
85
  import pino from "pino";
@@ -85,7 +101,7 @@ var BaseService = class extends Hookified {
85
101
  if (options && options.throwErrors !== void 0) {
86
102
  this._throwErrors = options.throwErrors;
87
103
  }
88
- this._net = new CacheableNet({
104
+ this._net = new CacheableNet2({
89
105
  cache: this._cache
90
106
  });
91
107
  }
@@ -123,59 +139,56 @@ var BaseService = class extends Hookified {
123
139
  this._log.info(message, ...args);
124
140
  this.emit("info", message, ...args);
125
141
  }
126
- async get(url, config2) {
142
+ async get(url, config) {
127
143
  let finalUrl = url;
128
- if (config2?.params) {
129
- const params = new URLSearchParams(config2.params);
144
+ if (config?.params) {
145
+ const params = new URLSearchParams(config.params);
130
146
  finalUrl = `${url}?${params.toString()}`;
131
147
  }
132
- const { params: _, ...fetchConfig } = config2 || {};
148
+ const { params: _, ...fetchConfig } = config || {};
133
149
  const response = await this._net.get(finalUrl, fetchConfig);
134
150
  return {
135
151
  data: response.data,
136
152
  status: response.response.status,
137
153
  statusText: response.response.statusText,
138
154
  headers: response.response.headers,
139
- config: config2,
155
+ config,
140
156
  request: void 0
141
157
  };
142
158
  }
143
- async post(url, data, config2) {
144
- const response = await this._net.post(url, data, config2);
159
+ async post(url, data, config) {
160
+ const response = await this._net.post(url, data, config);
145
161
  return {
146
162
  data: response.data,
147
163
  status: response.response.status,
148
164
  statusText: response.response.statusText,
149
165
  headers: response.response.headers,
150
- config: config2,
166
+ config,
151
167
  request: void 0
152
168
  };
153
169
  }
154
- async put(url, data, config2) {
155
- const response = await this._net.put(url, data, config2);
170
+ async put(url, data, config) {
171
+ const response = await this._net.put(url, data, config);
156
172
  return {
157
173
  data: response.data,
158
174
  status: response.response.status,
159
175
  statusText: response.response.statusText,
160
176
  headers: response.response.headers,
161
- config: config2,
177
+ config,
162
178
  request: void 0
163
179
  };
164
180
  }
165
- async delete(url, config2) {
181
+ async delete(url, config) {
166
182
  const headers = {
167
- ...config2?.headers
183
+ ...config?.headers
168
184
  };
169
185
  if (headers) {
170
186
  delete headers["content-type"];
171
187
  }
172
- const { data: configData, ...restConfig } = config2 || {};
188
+ const { data: configData, ...restConfig } = config || {};
173
189
  let body;
174
190
  if (configData) {
175
- body = typeof configData === "string" ? (
176
- /* c8 ignore next */
177
- configData
178
- ) : JSON.stringify(configData);
191
+ body = typeof configData === "string" ? configData : JSON.stringify(configData);
179
192
  if (!headers["content-type"] && !headers["Content-Type"]) {
180
193
  headers["content-type"] = "application/json";
181
194
  }
@@ -200,18 +213,18 @@ var BaseService = class extends Hookified {
200
213
  status: response.status,
201
214
  statusText: response.statusText,
202
215
  headers: response.headers,
203
- config: config2,
216
+ config,
204
217
  request: void 0
205
218
  };
206
219
  }
207
- async patch(url, data, config2) {
208
- const response = await this._net.patch(url, data, config2);
220
+ async patch(url, data, config) {
221
+ const response = await this._net.patch(url, data, config);
209
222
  return {
210
223
  data: response.data,
211
224
  status: response.response.status,
212
225
  statusText: response.response.statusText,
213
226
  headers: response.response.headers,
214
- config: config2,
227
+ config,
215
228
  request: void 0
216
229
  };
217
230
  }
@@ -688,6 +701,7 @@ var NetInfo = class extends BaseService {
688
701
  const errorResult = {
689
702
  ip,
690
703
  type: "error",
704
+ /* v8 ignore next -- @preserve */
691
705
  errorMessage: error instanceof Error ? error.message : "Unknown error"
692
706
  };
693
707
  return errorResult;
@@ -731,7 +745,7 @@ var NetInfo = class extends BaseService {
731
745
  };
732
746
 
733
747
  // src/toggle.ts
734
- import { CacheableNet as CacheableNet2 } from "@cacheable/net";
748
+ import { CacheableNet as CacheableNet3 } from "@cacheable/net";
735
749
  import { Hookified as Hookified2 } from "hookified";
736
750
  var Toggle = class extends Hookified2 {
737
751
  static {
@@ -742,7 +756,7 @@ var Toggle = class extends Hookified2 {
742
756
  _applicationId;
743
757
  _environment;
744
758
  _horizonUrls = [];
745
- _net = new CacheableNet2();
759
+ _net = new CacheableNet3();
746
760
  _defaultContext;
747
761
  _defaultTargetingKey = `${Math.random().toString(36).substring(7)}`;
748
762
  /**
@@ -974,6 +988,7 @@ var Toggle = class extends Hookified2 {
974
988
  try {
975
989
  const context = {
976
990
  application: this._applicationId ?? "",
991
+ /* v8 ignore next -- @preserve */
977
992
  environment: this._environment ?? "development"
978
993
  };
979
994
  if (options?.context) {
@@ -1164,7 +1179,10 @@ var Toggle = class extends Hookified2 {
1164
1179
  const data = await response.json();
1165
1180
  return data;
1166
1181
  } catch (error) {
1167
- const fetchError = error instanceof Error ? error : new Error("Unknown fetch error");
1182
+ const fetchError = (
1183
+ /* v8 ignore next -- @preserve */
1184
+ error instanceof Error ? error : new Error("Unknown fetch error")
1185
+ );
1168
1186
  const statusMatch = fetchError.message.match(/status (\d{3})/);
1169
1187
  if (statusMatch) {
1170
1188
  const status = statusMatch[1];
@@ -1432,5 +1450,7 @@ export {
1432
1450
  Hyphen,
1433
1451
  Toggle,
1434
1452
  env,
1453
+ getExecutionContext,
1435
1454
  loadEnv
1436
1455
  };
1456
+ /* v8 ignore next -- @preserve */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyphen/sdk",
3
- "version": "2.0.1",
3
+ "version": "2.2.0",
4
4
  "description": "Hyphen SDK for Node.js",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
@@ -29,27 +29,29 @@
29
29
  ],
30
30
  "author": "Team Hyphen <hello@hyphen.ai>",
31
31
  "license": "MIT",
32
+ "engines": {
33
+ "node": ">=20.12.0"
34
+ },
32
35
  "devDependencies": {
33
- "@biomejs/biome": "^2.2.5",
34
- "@swc/core": "^1.13.20",
35
- "@types/node": "^24.7.2",
36
- "@vitest/coverage-v8": "^3.2.4",
37
- "rimraf": "^6.0.1",
36
+ "@biomejs/biome": "^2.3.8",
37
+ "@swc/core": "^1.15.3",
38
+ "@types/node": "^25.0.0",
39
+ "@vitest/coverage-v8": "^4.0.15",
40
+ "rimraf": "^6.1.2",
38
41
  "tsd": "^0.33.0",
39
- "tsup": "^8.5.0",
42
+ "tsup": "^8.5.1",
40
43
  "typescript": "^5.9.3",
41
- "vitest": "^3.2.4"
44
+ "vitest": "^4.0.15"
42
45
  },
43
46
  "files": [
44
47
  "dist",
45
48
  "LICENSE"
46
49
  ],
47
50
  "dependencies": {
48
- "@cacheable/net": "^2.0.1",
49
- "@faker-js/faker": "^10.0.0",
50
- "cacheable": "^2.1.0",
51
- "dotenv": "^17.2.3",
52
- "hookified": "^1.12.1",
53
- "pino": "^10.0.0"
51
+ "@cacheable/net": "^2.0.4",
52
+ "@faker-js/faker": "^10.2.0",
53
+ "cacheable": "^2.3.0",
54
+ "hookified": "^1.14.0",
55
+ "pino": "^10.1.1"
54
56
  }
55
57
  }