@smplkit/sdk 1.3.6 → 1.3.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -727,9 +727,9 @@ interface SmplClientOptions {
727
727
  */
728
728
  environment?: string;
729
729
  /**
730
- * Optional service name. When set, the SDK automatically registers
731
- * the service as a context instance and includes it in flag
732
- * evaluation context.
730
+ * Service name. The SDK automatically registers the service as a
731
+ * context instance and includes it in flag evaluation context.
732
+ * When omitted, resolved from the `SMPLKIT_SERVICE` environment variable.
733
733
  */
734
734
  service?: string;
735
735
  /**
@@ -759,7 +759,7 @@ declare class SmplClient {
759
759
  /** @internal */
760
760
  readonly _environment: string;
761
761
  /** @internal */
762
- readonly _service: string | null;
762
+ readonly _service: string;
763
763
  private _connected;
764
764
  private readonly _timeout;
765
765
  private readonly _appHttp;
@@ -788,29 +788,39 @@ declare class SmplClient {
788
788
  * the base class for generic handling or specific subclasses for
789
789
  * fine-grained control.
790
790
  */
791
+ /** A single error object from a JSON:API error response. */
792
+ interface ApiErrorObject {
793
+ status?: string;
794
+ title?: string;
795
+ detail?: string;
796
+ source?: Record<string, unknown>;
797
+ }
791
798
  /** Base exception for all smplkit SDK errors. */
792
799
  declare class SmplError extends Error {
793
800
  /** The HTTP status code, if the error originated from an HTTP response. */
794
801
  readonly statusCode?: number;
795
802
  /** The raw response body, if available. */
796
803
  readonly responseBody?: string;
797
- constructor(message: string, statusCode?: number, responseBody?: string);
804
+ /** Structured JSON:API error objects from the server response, if available. */
805
+ readonly errors: ReadonlyArray<ApiErrorObject>;
806
+ constructor(message: string, statusCode?: number, responseBody?: string, errors?: ApiErrorObject[]);
807
+ toString(): string;
798
808
  }
799
809
  /** Raised when a network request fails (e.g., DNS resolution, connection refused). */
800
810
  declare class SmplConnectionError extends SmplError {
801
- constructor(message: string, statusCode?: number, responseBody?: string);
811
+ constructor(message: string, statusCode?: number, responseBody?: string, errors?: ApiErrorObject[]);
802
812
  }
803
813
  /** Raised when an operation exceeds its timeout. */
804
814
  declare class SmplTimeoutError extends SmplError {
805
- constructor(message: string, statusCode?: number, responseBody?: string);
815
+ constructor(message: string, statusCode?: number, responseBody?: string, errors?: ApiErrorObject[]);
806
816
  }
807
817
  /** Raised when a requested resource does not exist (HTTP 404). */
808
818
  declare class SmplNotFoundError extends SmplError {
809
- constructor(message: string, statusCode?: number, responseBody?: string);
819
+ constructor(message: string, statusCode?: number, responseBody?: string, errors?: ApiErrorObject[]);
810
820
  }
811
821
  /** Raised when an operation conflicts with current state (HTTP 409). */
812
822
  declare class SmplConflictError extends SmplError {
813
- constructor(message: string, statusCode?: number, responseBody?: string);
823
+ constructor(message: string, statusCode?: number, responseBody?: string, errors?: ApiErrorObject[]);
814
824
  }
815
825
  /** Raised when a method requiring connect() is called before connecting. */
816
826
  declare class SmplNotConnectedError extends SmplError {
@@ -818,7 +828,7 @@ declare class SmplNotConnectedError extends SmplError {
818
828
  }
819
829
  /** Raised when the server rejects a request due to validation errors (HTTP 422). */
820
830
  declare class SmplValidationError extends SmplError {
821
- constructor(message: string, statusCode?: number, responseBody?: string);
831
+ constructor(message: string, statusCode?: number, responseBody?: string, errors?: ApiErrorObject[]);
822
832
  }
823
833
 
824
- export { BoolFlagHandle, Config, type ConfigChangeEvent, ConfigClient, Context, ContextType, type CreateConfigOptions, Flag, FlagChangeEvent, FlagStats, type FlagType, FlagsClient, type GetConfigOptions, JsonFlagHandle, NumberFlagHandle, Rule, SharedWebSocket, SmplClient, type SmplClientOptions, SmplConflictError, SmplConnectionError, SmplError, SmplNotConnectedError, SmplNotFoundError, SmplTimeoutError, SmplValidationError, StringFlagHandle };
834
+ export { type ApiErrorObject, BoolFlagHandle, Config, type ConfigChangeEvent, ConfigClient, Context, ContextType, type CreateConfigOptions, Flag, FlagChangeEvent, FlagStats, type FlagType, FlagsClient, type GetConfigOptions, JsonFlagHandle, NumberFlagHandle, Rule, SharedWebSocket, SmplClient, type SmplClientOptions, SmplConflictError, SmplConnectionError, SmplError, SmplNotConnectedError, SmplNotFoundError, SmplTimeoutError, SmplValidationError, StringFlagHandle };
package/dist/index.d.ts CHANGED
@@ -727,9 +727,9 @@ interface SmplClientOptions {
727
727
  */
728
728
  environment?: string;
729
729
  /**
730
- * Optional service name. When set, the SDK automatically registers
731
- * the service as a context instance and includes it in flag
732
- * evaluation context.
730
+ * Service name. The SDK automatically registers the service as a
731
+ * context instance and includes it in flag evaluation context.
732
+ * When omitted, resolved from the `SMPLKIT_SERVICE` environment variable.
733
733
  */
734
734
  service?: string;
735
735
  /**
@@ -759,7 +759,7 @@ declare class SmplClient {
759
759
  /** @internal */
760
760
  readonly _environment: string;
761
761
  /** @internal */
762
- readonly _service: string | null;
762
+ readonly _service: string;
763
763
  private _connected;
764
764
  private readonly _timeout;
765
765
  private readonly _appHttp;
@@ -788,29 +788,39 @@ declare class SmplClient {
788
788
  * the base class for generic handling or specific subclasses for
789
789
  * fine-grained control.
790
790
  */
791
+ /** A single error object from a JSON:API error response. */
792
+ interface ApiErrorObject {
793
+ status?: string;
794
+ title?: string;
795
+ detail?: string;
796
+ source?: Record<string, unknown>;
797
+ }
791
798
  /** Base exception for all smplkit SDK errors. */
792
799
  declare class SmplError extends Error {
793
800
  /** The HTTP status code, if the error originated from an HTTP response. */
794
801
  readonly statusCode?: number;
795
802
  /** The raw response body, if available. */
796
803
  readonly responseBody?: string;
797
- constructor(message: string, statusCode?: number, responseBody?: string);
804
+ /** Structured JSON:API error objects from the server response, if available. */
805
+ readonly errors: ReadonlyArray<ApiErrorObject>;
806
+ constructor(message: string, statusCode?: number, responseBody?: string, errors?: ApiErrorObject[]);
807
+ toString(): string;
798
808
  }
799
809
  /** Raised when a network request fails (e.g., DNS resolution, connection refused). */
800
810
  declare class SmplConnectionError extends SmplError {
801
- constructor(message: string, statusCode?: number, responseBody?: string);
811
+ constructor(message: string, statusCode?: number, responseBody?: string, errors?: ApiErrorObject[]);
802
812
  }
803
813
  /** Raised when an operation exceeds its timeout. */
804
814
  declare class SmplTimeoutError extends SmplError {
805
- constructor(message: string, statusCode?: number, responseBody?: string);
815
+ constructor(message: string, statusCode?: number, responseBody?: string, errors?: ApiErrorObject[]);
806
816
  }
807
817
  /** Raised when a requested resource does not exist (HTTP 404). */
808
818
  declare class SmplNotFoundError extends SmplError {
809
- constructor(message: string, statusCode?: number, responseBody?: string);
819
+ constructor(message: string, statusCode?: number, responseBody?: string, errors?: ApiErrorObject[]);
810
820
  }
811
821
  /** Raised when an operation conflicts with current state (HTTP 409). */
812
822
  declare class SmplConflictError extends SmplError {
813
- constructor(message: string, statusCode?: number, responseBody?: string);
823
+ constructor(message: string, statusCode?: number, responseBody?: string, errors?: ApiErrorObject[]);
814
824
  }
815
825
  /** Raised when a method requiring connect() is called before connecting. */
816
826
  declare class SmplNotConnectedError extends SmplError {
@@ -818,7 +828,7 @@ declare class SmplNotConnectedError extends SmplError {
818
828
  }
819
829
  /** Raised when the server rejects a request due to validation errors (HTTP 422). */
820
830
  declare class SmplValidationError extends SmplError {
821
- constructor(message: string, statusCode?: number, responseBody?: string);
831
+ constructor(message: string, statusCode?: number, responseBody?: string, errors?: ApiErrorObject[]);
822
832
  }
823
833
 
824
- export { BoolFlagHandle, Config, type ConfigChangeEvent, ConfigClient, Context, ContextType, type CreateConfigOptions, Flag, FlagChangeEvent, FlagStats, type FlagType, FlagsClient, type GetConfigOptions, JsonFlagHandle, NumberFlagHandle, Rule, SharedWebSocket, SmplClient, type SmplClientOptions, SmplConflictError, SmplConnectionError, SmplError, SmplNotConnectedError, SmplNotFoundError, SmplTimeoutError, SmplValidationError, StringFlagHandle };
834
+ export { type ApiErrorObject, BoolFlagHandle, Config, type ConfigChangeEvent, ConfigClient, Context, ContextType, type CreateConfigOptions, Flag, FlagChangeEvent, FlagStats, type FlagType, FlagsClient, type GetConfigOptions, JsonFlagHandle, NumberFlagHandle, Rule, SharedWebSocket, SmplClient, type SmplClientOptions, SmplConflictError, SmplConnectionError, SmplError, SmplNotConnectedError, SmplNotFoundError, SmplTimeoutError, SmplValidationError, StringFlagHandle };
package/dist/index.js CHANGED
@@ -10,38 +10,54 @@ var SmplError = class extends Error {
10
10
  statusCode;
11
11
  /** The raw response body, if available. */
12
12
  responseBody;
13
- constructor(message, statusCode, responseBody) {
13
+ /** Structured JSON:API error objects from the server response, if available. */
14
+ errors;
15
+ constructor(message, statusCode, responseBody, errors) {
14
16
  super(message);
15
17
  this.name = "SmplError";
16
18
  this.statusCode = statusCode;
17
19
  this.responseBody = responseBody;
20
+ this.errors = errors ?? [];
18
21
  Object.setPrototypeOf(this, new.target.prototype);
19
22
  }
23
+ toString() {
24
+ if (this.errors.length === 0) {
25
+ return `${this.name}: ${this.message}`;
26
+ }
27
+ if (this.errors.length === 1) {
28
+ return `${this.name}: ${this.message}
29
+ Error: ${JSON.stringify(this.errors[0])}`;
30
+ }
31
+ const lines = this.errors.map((e, i) => ` [${i}] ${JSON.stringify(e)}`);
32
+ return `${this.name}: ${this.message}
33
+ Errors:
34
+ ${lines.join("\n")}`;
35
+ }
20
36
  };
21
37
  var SmplConnectionError = class extends SmplError {
22
- constructor(message, statusCode, responseBody) {
23
- super(message, statusCode, responseBody);
38
+ constructor(message, statusCode, responseBody, errors) {
39
+ super(message, statusCode, responseBody, errors);
24
40
  this.name = "SmplConnectionError";
25
41
  Object.setPrototypeOf(this, new.target.prototype);
26
42
  }
27
43
  };
28
44
  var SmplTimeoutError = class extends SmplError {
29
- constructor(message, statusCode, responseBody) {
30
- super(message, statusCode, responseBody);
45
+ constructor(message, statusCode, responseBody, errors) {
46
+ super(message, statusCode, responseBody, errors);
31
47
  this.name = "SmplTimeoutError";
32
48
  Object.setPrototypeOf(this, new.target.prototype);
33
49
  }
34
50
  };
35
51
  var SmplNotFoundError = class extends SmplError {
36
- constructor(message, statusCode, responseBody) {
37
- super(message, statusCode ?? 404, responseBody);
52
+ constructor(message, statusCode, responseBody, errors) {
53
+ super(message, statusCode ?? 404, responseBody, errors);
38
54
  this.name = "SmplNotFoundError";
39
55
  Object.setPrototypeOf(this, new.target.prototype);
40
56
  }
41
57
  };
42
58
  var SmplConflictError = class extends SmplError {
43
- constructor(message, statusCode, responseBody) {
44
- super(message, statusCode ?? 409, responseBody);
59
+ constructor(message, statusCode, responseBody, errors) {
60
+ super(message, statusCode ?? 409, responseBody, errors);
45
61
  this.name = "SmplConflictError";
46
62
  Object.setPrototypeOf(this, new.target.prototype);
47
63
  }
@@ -54,12 +70,53 @@ var SmplNotConnectedError = class extends SmplError {
54
70
  }
55
71
  };
56
72
  var SmplValidationError = class extends SmplError {
57
- constructor(message, statusCode, responseBody) {
58
- super(message, statusCode ?? 422, responseBody);
73
+ constructor(message, statusCode, responseBody, errors) {
74
+ super(message, statusCode ?? 422, responseBody, errors);
59
75
  this.name = "SmplValidationError";
60
76
  Object.setPrototypeOf(this, new.target.prototype);
61
77
  }
62
78
  };
79
+ function parseJsonApiErrors(body) {
80
+ try {
81
+ const parsed = JSON.parse(body);
82
+ if (parsed && Array.isArray(parsed.errors)) {
83
+ return parsed.errors.map((e) => ({
84
+ ...e.status !== void 0 ? { status: String(e.status) } : {},
85
+ ...e.title !== void 0 ? { title: String(e.title) } : {},
86
+ ...e.detail !== void 0 ? { detail: String(e.detail) } : {},
87
+ ...e.source !== void 0 && typeof e.source === "object" && e.source !== null ? { source: e.source } : {}
88
+ }));
89
+ }
90
+ } catch {
91
+ }
92
+ return [];
93
+ }
94
+ function deriveMessage(errors, statusCode, body) {
95
+ if (errors.length === 0) {
96
+ return body ? `HTTP ${statusCode}: ${body}` : `HTTP ${statusCode}`;
97
+ }
98
+ const first = errors[0];
99
+ const base = first.detail ?? first.title ?? (first.status ? `HTTP ${first.status}` : `HTTP ${statusCode}`);
100
+ if (errors.length > 1) {
101
+ return `${base} (and ${errors.length - 1} more error${errors.length - 1 > 1 ? "s" : ""})`;
102
+ }
103
+ return base;
104
+ }
105
+ function throwForStatus(statusCode, body) {
106
+ const errors = parseJsonApiErrors(body);
107
+ const message = deriveMessage(errors, statusCode, body);
108
+ switch (statusCode) {
109
+ case 400:
110
+ case 422:
111
+ throw new SmplValidationError(message, statusCode, body, errors);
112
+ case 404:
113
+ throw new SmplNotFoundError(message, statusCode, body, errors);
114
+ case 409:
115
+ throw new SmplConflictError(message, statusCode, body, errors);
116
+ default:
117
+ throw new SmplError(message, statusCode, body, errors);
118
+ }
119
+ }
63
120
 
64
121
  // src/config/resolve.ts
65
122
  function deepMerge(base, override) {
@@ -280,18 +337,9 @@ function resourceToConfig(resource, client) {
280
337
  updatedAt: attrs.updated_at ? new Date(attrs.updated_at) : null
281
338
  });
282
339
  }
283
- async function checkError(response, context) {
340
+ async function checkError(response, _context) {
284
341
  const body = await response.text().catch(() => "");
285
- switch (response.status) {
286
- case 404:
287
- throw new SmplNotFoundError(body || context, 404, body);
288
- case 409:
289
- throw new SmplConflictError(body || context, 409, body);
290
- case 422:
291
- throw new SmplValidationError(body || context, 422, body);
292
- default:
293
- throw new SmplError(`HTTP ${response.status}: ${body}`, response.status, body);
294
- }
342
+ throwForStatus(response.status, body);
295
343
  }
296
344
  function wrapFetchError(err) {
297
345
  if (err instanceof SmplNotFoundError || err instanceof SmplConflictError || err instanceof SmplValidationError || err instanceof SmplError) {
@@ -794,18 +842,9 @@ var APP_BASE_URL = "https://app.smplkit.com";
794
842
  var CACHE_MAX_SIZE = 1e4;
795
843
  var CONTEXT_REGISTRATION_LRU_SIZE = 1e4;
796
844
  var CONTEXT_BATCH_FLUSH_SIZE = 100;
797
- async function checkError2(response, context) {
845
+ async function checkError2(response, _context) {
798
846
  const body = await response.text().catch(() => "");
799
- switch (response.status) {
800
- case 404:
801
- throw new SmplNotFoundError(body || context, 404, body);
802
- case 409:
803
- throw new SmplConflictError(body || context, 409, body);
804
- case 422:
805
- throw new SmplValidationError(body || context, 422, body);
806
- default:
807
- throw new SmplError(`HTTP ${response.status}: ${body}`, response.status, body);
808
- }
847
+ throwForStatus(response.status, body);
809
848
  }
810
849
  function wrapFetchError2(err) {
811
850
  if (err instanceof SmplNotFoundError || err instanceof SmplConflictError || err instanceof SmplValidationError || err instanceof SmplError) {
@@ -1784,38 +1823,61 @@ var SharedWebSocket = class {
1784
1823
  import { readFileSync } from "fs";
1785
1824
  import { homedir } from "os";
1786
1825
  import { join } from "path";
1787
- var NO_API_KEY_MESSAGE = "No API key provided. Set one of:\n 1. Pass apiKey to the constructor\n 2. Set the SMPLKIT_API_KEY environment variable\n 3. Create a ~/.smplkit file with:\n [default]\n api_key = your_key_here";
1788
- function resolveApiKey(explicit) {
1789
- if (explicit) return explicit;
1790
- const envVal = process.env.SMPLKIT_API_KEY;
1791
- if (envVal) return envVal;
1826
+ function noApiKeyMessage(environment) {
1827
+ return `No API key provided. Set one of:
1828
+ 1. Pass apiKey to the constructor
1829
+ 2. Set the SMPLKIT_API_KEY environment variable
1830
+ 3. Create a ~/.smplkit file with:
1831
+ [${environment}]
1832
+ api_key = your_key_here`;
1833
+ }
1834
+ function readApiKeyFromConfig(environment) {
1792
1835
  const configPath = join(homedir(), ".smplkit");
1793
1836
  try {
1794
1837
  const content = readFileSync(configPath, "utf-8");
1795
- let inDefaultSection = false;
1838
+ let currentSection = null;
1839
+ let envKey;
1840
+ let defaultKey;
1796
1841
  for (const line of content.split("\n")) {
1797
1842
  const trimmed = line.trim();
1798
1843
  if (trimmed === "" || trimmed.startsWith("#")) continue;
1799
1844
  if (trimmed.startsWith("[")) {
1800
- inDefaultSection = trimmed.toLowerCase() === "[default]";
1845
+ const sectionName = trimmed.slice(1, trimmed.indexOf("]")).toLowerCase();
1846
+ currentSection = sectionName;
1801
1847
  continue;
1802
1848
  }
1803
- if (inDefaultSection && trimmed.startsWith("api_key")) {
1849
+ if (currentSection && trimmed.startsWith("api_key")) {
1804
1850
  const eqIndex = trimmed.indexOf("=");
1805
1851
  if (eqIndex !== -1) {
1806
1852
  const value = trimmed.slice(eqIndex + 1).trim();
1807
- if (value) return value;
1853
+ if (value) {
1854
+ if (currentSection === environment.toLowerCase()) {
1855
+ envKey = value;
1856
+ } else if (currentSection === "default") {
1857
+ defaultKey = value;
1858
+ }
1859
+ }
1808
1860
  }
1809
1861
  }
1810
1862
  }
1863
+ return envKey ?? defaultKey;
1811
1864
  } catch {
1865
+ return void 0;
1812
1866
  }
1813
- throw new SmplError(NO_API_KEY_MESSAGE);
1867
+ }
1868
+ function resolveApiKey(explicit, environment) {
1869
+ if (explicit) return explicit;
1870
+ const envVal = process.env.SMPLKIT_API_KEY;
1871
+ if (envVal) return envVal;
1872
+ const fileKey = readApiKeyFromConfig(environment);
1873
+ if (fileKey) return fileKey;
1874
+ throw new SmplError(noApiKeyMessage(environment));
1814
1875
  }
1815
1876
 
1816
1877
  // src/client.ts
1817
1878
  var APP_BASE_URL2 = "https://app.smplkit.com";
1818
1879
  var NO_ENVIRONMENT_MESSAGE = "No environment provided. Set one of:\n 1. Pass environment to the constructor\n 2. Set the SMPLKIT_ENVIRONMENT environment variable";
1880
+ var NO_SERVICE_MESSAGE = "No service provided. Set one of:\n 1. Pass service in options\n 2. Set the SMPLKIT_SERVICE environment variable";
1819
1881
  var SmplClient = class {
1820
1882
  /** Client for config management-plane operations. */
1821
1883
  config;
@@ -1831,14 +1893,18 @@ var SmplClient = class {
1831
1893
  _timeout;
1832
1894
  _appHttp;
1833
1895
  constructor(options = {}) {
1834
- const apiKey = resolveApiKey(options.apiKey);
1835
- this._apiKey = apiKey;
1836
1896
  const environment = options.environment || process.env.SMPLKIT_ENVIRONMENT;
1837
1897
  if (!environment) {
1838
1898
  throw new SmplError(NO_ENVIRONMENT_MESSAGE);
1839
1899
  }
1840
1900
  this._environment = environment;
1841
- this._service = options.service || process.env.SMPLKIT_SERVICE || null;
1901
+ const service = options.service || process.env.SMPLKIT_SERVICE;
1902
+ if (!service) {
1903
+ throw new SmplError(NO_SERVICE_MESSAGE);
1904
+ }
1905
+ this._service = service;
1906
+ const apiKey = resolveApiKey(options.apiKey, environment);
1907
+ this._apiKey = apiKey;
1842
1908
  this._timeout = options.timeout ?? 3e4;
1843
1909
  const ms = this._timeout;
1844
1910
  this._appHttp = createClient3({
@@ -1878,9 +1944,7 @@ var SmplClient = class {
1878
1944
  */
1879
1945
  async connect() {
1880
1946
  if (this._connected) return;
1881
- if (this._service) {
1882
- await this._registerServiceContext();
1883
- }
1947
+ await this._registerServiceContext();
1884
1948
  await this.flags._connectInternal(this._environment);
1885
1949
  await this.config._connectInternal(this._environment);
1886
1950
  this._connected = true;