@sylphx/sdk 0.0.1 → 0.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/dist/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  // src/constants.ts
2
2
  var DEFAULT_PLATFORM_URL = "https://sylphx.com";
3
- var SDK_API_PATH = `/api/app/v1`;
3
+ var SDK_API_PATH = `/api/v1`;
4
4
  var SDK_API_PATH_NEW = `/v1`;
5
5
  var DEFAULT_SDK_API_HOST = "api.sylphx.com";
6
6
  var SDK_VERSION = "0.1.0";
@@ -451,6 +451,7 @@ function httpStatusToErrorCode(status) {
451
451
  return status >= 500 ? "INTERNAL_SERVER_ERROR" : "BAD_REQUEST";
452
452
  }
453
453
  }
454
+ var REF_PATTERN = /^[a-z0-9]{16}$/;
454
455
  function createConfig(input) {
455
456
  let secretKey;
456
457
  if (input.secretKey) {
@@ -466,13 +467,22 @@ function createConfig(input) {
466
467
  }
467
468
  secretKey = result.sanitizedKey;
468
469
  }
470
+ if (input.ref !== void 0) {
471
+ const trimmedRef = input.ref.trim();
472
+ if (!REF_PATTERN.test(trimmedRef)) {
473
+ throw new SylphxError(
474
+ `[Sylphx] Invalid project ref format: "${input.ref}". Expected a 16-character lowercase alphanumeric string (e.g. "abc123def456ghij"). Get your ref from Platform Console \u2192 Projects \u2192 Your Project \u2192 Overview.`,
475
+ { code: "BAD_REQUEST" }
476
+ );
477
+ }
478
+ }
469
479
  let platformUrl;
470
480
  let apiBasePath;
471
481
  if (input.platformUrl) {
472
482
  platformUrl = input.platformUrl.trim();
473
483
  apiBasePath = SDK_API_PATH;
474
484
  } else if (input.ref) {
475
- platformUrl = `https://${input.ref}.${DEFAULT_SDK_API_HOST}`;
485
+ platformUrl = `https://${input.ref.trim()}.${DEFAULT_SDK_API_HOST}`;
476
486
  apiBasePath = SDK_API_PATH_NEW;
477
487
  } else {
478
488
  platformUrl = DEFAULT_PLATFORM_URL;
@@ -776,6 +786,7 @@ function createDeduplicationMiddleware(config = {}) {
776
786
  deduped._dedupKey = key;
777
787
  return deduped;
778
788
  }
789
+ ;
779
790
  request._dedupKey = key;
780
791
  return request;
781
792
  },
@@ -799,30 +810,24 @@ function createDeduplicationMiddleware(config = {}) {
799
810
  var CircuitBreakerOpenError = class extends Error {
800
811
  remainingMs;
801
812
  constructor(remainingMs) {
802
- super(
803
- `Circuit breaker is open. Retry after ${Math.ceil(remainingMs / 1e3)}s`
804
- );
813
+ super(`Circuit breaker is open. Retry after ${Math.ceil(remainingMs / 1e3)}s`);
805
814
  this.name = "CircuitBreakerOpenError";
806
815
  this.remainingMs = remainingMs;
807
816
  }
808
817
  };
809
- var circuitBreaker = null;
810
- function getCircuitBreaker(config = {}) {
811
- if (!circuitBreaker) {
812
- circuitBreaker = {
813
- state: "CLOSED",
814
- failures: [],
815
- openedAt: null,
816
- config: {
817
- enabled: config.enabled ?? true,
818
- failureThreshold: config.failureThreshold ?? CIRCUIT_BREAKER_FAILURE_THRESHOLD,
819
- windowMs: config.windowMs ?? CIRCUIT_BREAKER_WINDOW_MS,
820
- openDurationMs: config.openDurationMs ?? CIRCUIT_BREAKER_OPEN_DURATION_MS,
821
- isFailure: config.isFailure ?? ((status) => status >= 500 || status === 429)
822
- }
823
- };
824
- }
825
- return circuitBreaker;
818
+ function createCircuitBreakerInstance(config = {}) {
819
+ return {
820
+ state: "CLOSED",
821
+ failures: [],
822
+ openedAt: null,
823
+ config: {
824
+ enabled: config.enabled ?? true,
825
+ failureThreshold: config.failureThreshold ?? CIRCUIT_BREAKER_FAILURE_THRESHOLD,
826
+ windowMs: config.windowMs ?? CIRCUIT_BREAKER_WINDOW_MS,
827
+ openDurationMs: config.openDurationMs ?? CIRCUIT_BREAKER_OPEN_DURATION_MS,
828
+ isFailure: config.isFailure ?? ((status) => status >= 500 || status === 429)
829
+ }
830
+ };
826
831
  }
827
832
  function recordFailure(cb) {
828
833
  const now = Date.now();
@@ -870,7 +875,8 @@ function createCircuitBreakerMiddleware(config) {
870
875
  }
871
876
  };
872
877
  }
873
- const cb = getCircuitBreaker(config ?? {});
878
+ const cb = createCircuitBreakerInstance(config ?? {});
879
+ _lastCircuitBreaker = cb;
874
880
  return {
875
881
  async onRequest({ request }) {
876
882
  if (!cb.config.enabled) {
@@ -895,15 +901,21 @@ function createCircuitBreakerMiddleware(config) {
895
901
  }
896
902
  };
897
903
  }
904
+ var _lastCircuitBreaker = null;
898
905
  function resetCircuitBreaker() {
899
- circuitBreaker = null;
906
+ if (_lastCircuitBreaker) {
907
+ _lastCircuitBreaker.state = "CLOSED";
908
+ _lastCircuitBreaker.failures = [];
909
+ _lastCircuitBreaker.openedAt = null;
910
+ }
911
+ _lastCircuitBreaker = null;
900
912
  }
901
913
  function getCircuitBreakerState() {
902
- if (!circuitBreaker) return null;
914
+ if (!_lastCircuitBreaker) return null;
903
915
  return {
904
- state: circuitBreaker.state,
905
- failures: circuitBreaker.failures.length,
906
- openedAt: circuitBreaker.openedAt
916
+ state: _lastCircuitBreaker.state,
917
+ failures: _lastCircuitBreaker.failures.length,
918
+ openedAt: _lastCircuitBreaker.openedAt
907
919
  };
908
920
  }
909
921
  var etagCache = /* @__PURE__ */ new Map();
@@ -995,6 +1007,7 @@ function createETagMiddleware(config) {
995
1007
  }
996
1008
  };
997
1009
  }
1010
+ var retryBodyMap = /* @__PURE__ */ new WeakMap();
998
1011
  function createRetryMiddleware(retryConfig) {
999
1012
  if (retryConfig === false) {
1000
1013
  return {
@@ -1010,27 +1023,22 @@ function createRetryMiddleware(retryConfig) {
1010
1023
  shouldRetry = isRetryableStatus,
1011
1024
  timeout = DEFAULT_TIMEOUT_MS
1012
1025
  } = retryConfig ?? {};
1013
- let originalBody = null;
1014
1026
  return {
1015
1027
  async onRequest({ request }) {
1016
- if (request.body) {
1017
- originalBody = await request.clone().text();
1018
- } else {
1019
- originalBody = null;
1020
- }
1021
- if (!request.signal) {
1022
- const controller = new AbortController();
1023
- setTimeout(() => controller.abort(), timeout);
1024
- return new Request(request.url, {
1025
- method: request.method,
1026
- headers: request.headers,
1027
- body: originalBody,
1028
- signal: controller.signal
1029
- });
1030
- }
1031
- return request;
1028
+ const body = request.body ? await request.clone().text() : null;
1029
+ const controller = new AbortController();
1030
+ setTimeout(() => controller.abort(), timeout);
1031
+ const newRequest = new Request(request.url, {
1032
+ method: request.method,
1033
+ headers: request.headers,
1034
+ body,
1035
+ signal: controller.signal
1036
+ });
1037
+ retryBodyMap.set(newRequest, body);
1038
+ return newRequest;
1032
1039
  },
1033
1040
  async onResponse({ response, request }) {
1041
+ const originalBody = retryBodyMap.get(request) ?? null;
1034
1042
  let attempt = 0;
1035
1043
  let currentResponse = response;
1036
1044
  while (attempt < maxRetries && shouldRetry(currentResponse.status, attempt)) {
@@ -1050,16 +1058,19 @@ function createRetryMiddleware(retryConfig) {
1050
1058
  const newResponse = await fetch(retryRequest);
1051
1059
  clearTimeout(timeoutId);
1052
1060
  if (newResponse.ok || !shouldRetry(newResponse.status, attempt)) {
1061
+ retryBodyMap.delete(request);
1053
1062
  return newResponse;
1054
1063
  }
1055
1064
  currentResponse = newResponse;
1056
1065
  } catch (error) {
1057
1066
  clearTimeout(timeoutId);
1058
1067
  if (attempt >= maxRetries) {
1068
+ retryBodyMap.delete(request);
1059
1069
  throw error;
1060
1070
  }
1061
1071
  }
1062
1072
  }
1073
+ retryBodyMap.delete(request);
1063
1074
  return currentResponse;
1064
1075
  }
1065
1076
  };
@@ -1148,35 +1159,20 @@ async function signOut(config) {
1148
1159
  await callApi(config, "/auth/logout", { method: "POST" });
1149
1160
  }
1150
1161
  async function refreshToken(config, token) {
1151
- const response = await fetch(`${config.platformUrl}/api/v1/auth/token`, {
1162
+ return callApi(config, "/auth/token", {
1152
1163
  method: "POST",
1153
- headers: { "Content-Type": "application/json" },
1154
- body: JSON.stringify({
1164
+ body: {
1155
1165
  grant_type: "refresh_token",
1156
1166
  refresh_token: token,
1157
1167
  client_secret: config.secretKey
1158
- })
1168
+ }
1159
1169
  });
1160
- if (!response.ok) {
1161
- const error = await response.json().catch(() => ({ message: "Token refresh failed" }));
1162
- throw new SylphxError(error.message ?? "Token refresh failed", {
1163
- code: "UNAUTHORIZED"
1164
- });
1165
- }
1166
- return response.json();
1167
1170
  }
1168
1171
  async function verifyEmail(config, token) {
1169
- const response = await fetch(`${config.platformUrl}/api/v1/auth/verify-email`, {
1172
+ await callApi(config, "/auth/verify-email", {
1170
1173
  method: "POST",
1171
- headers: buildHeaders(config),
1172
- body: JSON.stringify({ token })
1174
+ body: { token }
1173
1175
  });
1174
- if (!response.ok) {
1175
- const error = await response.json().catch(() => ({ message: "Email verification failed" }));
1176
- throw new SylphxError(error.message ?? "Email verification failed", {
1177
- code: "BAD_REQUEST"
1178
- });
1179
- }
1180
1176
  }
1181
1177
  async function forgotPassword(config, email) {
1182
1178
  await callApi(config, "/auth/forgot-password", {