@hyphen/sdk 2.0.2 → 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,53 +177,53 @@ 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
229
  body = typeof configData === "string" ? configData : JSON.stringify(configData);
@@ -234,18 +251,18 @@ var BaseService = class extends import_hookified.Hookified {
234
251
  status: response.status,
235
252
  statusText: response.statusText,
236
253
  headers: response.headers,
237
- config: config2,
254
+ config,
238
255
  request: void 0
239
256
  };
240
257
  }
241
- async patch(url, data, config2) {
242
- 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);
243
260
  return {
244
261
  data: response.data,
245
262
  status: response.response.status,
246
263
  statusText: response.response.statusText,
247
264
  headers: response.response.headers,
248
- config: config2,
265
+ config,
249
266
  request: void 0
250
267
  };
251
268
  }
@@ -722,6 +739,7 @@ var NetInfo = class extends BaseService {
722
739
  const errorResult = {
723
740
  ip,
724
741
  type: "error",
742
+ /* v8 ignore next -- @preserve */
725
743
  errorMessage: error instanceof Error ? error.message : "Unknown error"
726
744
  };
727
745
  return errorResult;
@@ -765,7 +783,7 @@ var NetInfo = class extends BaseService {
765
783
  };
766
784
 
767
785
  // src/toggle.ts
768
- var import_net2 = require("@cacheable/net");
786
+ var import_net3 = require("@cacheable/net");
769
787
  var import_hookified2 = require("hookified");
770
788
  var Toggle = class extends import_hookified2.Hookified {
771
789
  static {
@@ -776,7 +794,7 @@ var Toggle = class extends import_hookified2.Hookified {
776
794
  _applicationId;
777
795
  _environment;
778
796
  _horizonUrls = [];
779
- _net = new import_net2.CacheableNet();
797
+ _net = new import_net3.CacheableNet();
780
798
  _defaultContext;
781
799
  _defaultTargetingKey = `${Math.random().toString(36).substring(7)}`;
782
800
  /**
@@ -1008,6 +1026,7 @@ var Toggle = class extends import_hookified2.Hookified {
1008
1026
  try {
1009
1027
  const context = {
1010
1028
  application: this._applicationId ?? "",
1029
+ /* v8 ignore next -- @preserve */
1011
1030
  environment: this._environment ?? "development"
1012
1031
  };
1013
1032
  if (options?.context) {
@@ -1198,7 +1217,10 @@ var Toggle = class extends import_hookified2.Hookified {
1198
1217
  const data = await response.json();
1199
1218
  return data;
1200
1219
  } catch (error) {
1201
- 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
+ );
1202
1224
  const statusMatch = fetchError.message.match(/status (\d{3})/);
1203
1225
  if (statusMatch) {
1204
1226
  const status = statusMatch[1];
@@ -1467,6 +1489,7 @@ var Hyphen = class extends import_hookified3.Hookified {
1467
1489
  Hyphen,
1468
1490
  Toggle,
1469
1491
  env,
1492
+ getExecutionContext,
1470
1493
  loadEnv
1471
1494
  });
1472
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,53 +139,53 @@ 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
191
  body = typeof configData === "string" ? configData : JSON.stringify(configData);
@@ -197,18 +213,18 @@ var BaseService = class extends Hookified {
197
213
  status: response.status,
198
214
  statusText: response.statusText,
199
215
  headers: response.headers,
200
- config: config2,
216
+ config,
201
217
  request: void 0
202
218
  };
203
219
  }
204
- async patch(url, data, config2) {
205
- 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);
206
222
  return {
207
223
  data: response.data,
208
224
  status: response.response.status,
209
225
  statusText: response.response.statusText,
210
226
  headers: response.response.headers,
211
- config: config2,
227
+ config,
212
228
  request: void 0
213
229
  };
214
230
  }
@@ -685,6 +701,7 @@ var NetInfo = class extends BaseService {
685
701
  const errorResult = {
686
702
  ip,
687
703
  type: "error",
704
+ /* v8 ignore next -- @preserve */
688
705
  errorMessage: error instanceof Error ? error.message : "Unknown error"
689
706
  };
690
707
  return errorResult;
@@ -728,7 +745,7 @@ var NetInfo = class extends BaseService {
728
745
  };
729
746
 
730
747
  // src/toggle.ts
731
- import { CacheableNet as CacheableNet2 } from "@cacheable/net";
748
+ import { CacheableNet as CacheableNet3 } from "@cacheable/net";
732
749
  import { Hookified as Hookified2 } from "hookified";
733
750
  var Toggle = class extends Hookified2 {
734
751
  static {
@@ -739,7 +756,7 @@ var Toggle = class extends Hookified2 {
739
756
  _applicationId;
740
757
  _environment;
741
758
  _horizonUrls = [];
742
- _net = new CacheableNet2();
759
+ _net = new CacheableNet3();
743
760
  _defaultContext;
744
761
  _defaultTargetingKey = `${Math.random().toString(36).substring(7)}`;
745
762
  /**
@@ -971,6 +988,7 @@ var Toggle = class extends Hookified2 {
971
988
  try {
972
989
  const context = {
973
990
  application: this._applicationId ?? "",
991
+ /* v8 ignore next -- @preserve */
974
992
  environment: this._environment ?? "development"
975
993
  };
976
994
  if (options?.context) {
@@ -1161,7 +1179,10 @@ var Toggle = class extends Hookified2 {
1161
1179
  const data = await response.json();
1162
1180
  return data;
1163
1181
  } catch (error) {
1164
- 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
+ );
1165
1186
  const statusMatch = fetchError.message.match(/status (\d{3})/);
1166
1187
  if (statusMatch) {
1167
1188
  const status = statusMatch[1];
@@ -1429,6 +1450,7 @@ export {
1429
1450
  Hyphen,
1430
1451
  Toggle,
1431
1452
  env,
1453
+ getExecutionContext,
1432
1454
  loadEnv
1433
1455
  };
1434
1456
  /* v8 ignore next -- @preserve */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyphen/sdk",
3
- "version": "2.0.2",
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.7",
34
- "@swc/core": "^1.13.20",
35
- "@types/node": "^24.9.1",
36
- "@vitest/coverage-v8": "^4.0.2",
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": "^4.0.2"
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.1.0",
50
- "cacheable": "^2.1.1",
51
- "dotenv": "^17.2.3",
52
- "hookified": "^1.12.2",
53
- "pino": "^10.1.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
  }