@relyio/sdk 0.2.1 → 0.3.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.
Files changed (35) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/{chunk-7W4MJXQ4.cjs → chunk-54WP2PWG.cjs} +215 -96
  4. package/dist/chunk-BBUB7C2Z.js +81 -0
  5. package/dist/{chunk-FJK7ZWKK.cjs → chunk-HMVGXUGR.cjs} +156 -221
  6. package/dist/chunk-LSRPMNPT.cjs +89 -0
  7. package/dist/{chunk-2QUK2ZGD.js → chunk-LYRYCIX5.js} +157 -219
  8. package/dist/{chunk-RJBIJ2ZK.js → chunk-QIZOBT5F.js} +215 -96
  9. package/dist/client/index.cjs +4 -3
  10. package/dist/client/index.d.cts +4 -3
  11. package/dist/client/index.d.ts +4 -3
  12. package/dist/client/index.js +2 -1
  13. package/dist/define-content-type-CBfnTWSS.d.cts +716 -0
  14. package/dist/define-content-type-CBfnTWSS.d.ts +716 -0
  15. package/dist/{define-schema-DHvUCKwj.d.cts → define-schema-BD6HSPnG.d.ts} +55 -722
  16. package/dist/{define-schema-DHvUCKwj.d.ts → define-schema-BfXeR6K8.d.cts} +55 -722
  17. package/dist/dev-handshake/index.cjs +37 -0
  18. package/dist/dev-handshake/index.d.cts +144 -0
  19. package/dist/dev-handshake/index.d.ts +144 -0
  20. package/dist/dev-handshake/index.js +4 -0
  21. package/dist/{index-DGWaawHt.d.cts → index-BDpar3FO.d.cts} +22 -6
  22. package/dist/{index-BsUqCAu8.d.cts → index-DuEhsGdE.d.ts} +516 -2155
  23. package/dist/{index-B-KiFHbx.d.ts → index-Dx01iwaf.d.ts} +22 -6
  24. package/dist/{index-BsUqCAu8.d.ts → index-ulOrcfPa.d.cts} +516 -2155
  25. package/dist/index.cjs +67 -70
  26. package/dist/index.d.cts +28 -13
  27. package/dist/index.d.ts +28 -13
  28. package/dist/index.js +3 -2
  29. package/dist/schema/index.cjs +35 -47
  30. package/dist/schema/index.d.cts +3 -2
  31. package/dist/schema/index.d.ts +3 -2
  32. package/dist/schema/index.js +1 -1
  33. package/dist/types/index.d.cts +2 -1
  34. package/dist/types/index.d.ts +2 -1
  35. package/package.json +11 -5
@@ -1,5 +1,6 @@
1
1
  'use strict';
2
2
 
3
+ var chunkLSRPMNPT_cjs = require('./chunk-LSRPMNPT.cjs');
3
4
  var chunkMWMSFIWN_cjs = require('./chunk-MWMSFIWN.cjs');
4
5
 
5
6
  /* RelyIO SDK - Built with ❤️ by the RelyIO Team */
@@ -167,14 +168,17 @@ var _UsageQuotaExceededError = class _UsageQuotaExceededError extends SDKError {
167
168
  chunkMWMSFIWN_cjs.__name(_UsageQuotaExceededError, "UsageQuotaExceededError");
168
169
  var UsageQuotaExceededError = _UsageQuotaExceededError;
169
170
  var _RateLimitError = class _RateLimitError extends SDKError {
170
- constructor(message = "Rate limit exceeded", limit, resetAt, context) {
171
+ constructor(message = "Rate limit exceeded", limit, resetAt, context, retryAfterMs) {
171
172
  super(message, 429, void 0, context);
172
173
  chunkMWMSFIWN_cjs.__publicField(this, "code", "RATE_LIMIT_EXCEEDED");
173
174
  chunkMWMSFIWN_cjs.__publicField(this, "retryable", true);
174
175
  chunkMWMSFIWN_cjs.__publicField(this, "limit");
175
176
  chunkMWMSFIWN_cjs.__publicField(this, "resetAt");
177
+ /** `Retry-After`, in milliseconds, where the response carried a readable one. */
178
+ chunkMWMSFIWN_cjs.__publicField(this, "retryAfterMs");
176
179
  this.limit = limit;
177
180
  this.resetAt = resetAt;
181
+ this.retryAfterMs = retryAfterMs;
178
182
  }
179
183
  getUserMessage() {
180
184
  return this.resetAt === void 0 ? "Too many requests. Please retry shortly." : `Too many requests. The window resets at ${this.resetAt}.`;
@@ -223,12 +227,15 @@ var _TransactionRolledBackError = class _TransactionRolledBackError extends SDKE
223
227
  chunkMWMSFIWN_cjs.__name(_TransactionRolledBackError, "TransactionRolledBackError");
224
228
  var TransactionRolledBackError = _TransactionRolledBackError;
225
229
  var _ServiceIndeterminateError = class _ServiceIndeterminateError extends SDKError {
226
- constructor(message = "The service could not decide this request", missing, context) {
230
+ constructor(message = "The service could not decide this request", missing, context, retryAfterMs) {
227
231
  super(message, 503, void 0, context);
228
232
  chunkMWMSFIWN_cjs.__publicField(this, "code", "SERVICE_INDETERMINATE");
229
233
  chunkMWMSFIWN_cjs.__publicField(this, "retryable", true);
230
234
  chunkMWMSFIWN_cjs.__publicField(this, "missing");
235
+ /** `Retry-After`, in milliseconds. The contract requires the header on this code. */
236
+ chunkMWMSFIWN_cjs.__publicField(this, "retryAfterMs");
231
237
  this.missing = missing;
238
+ this.retryAfterMs = retryAfterMs;
232
239
  }
233
240
  getUserMessage() {
234
241
  return "The service is temporarily unable to answer. Please retry.";
@@ -459,6 +466,14 @@ var _InvalidFieldTypeError = class _InvalidFieldTypeError extends BaseValidation
459
466
  chunkMWMSFIWN_cjs.__name(_InvalidFieldTypeError, "InvalidFieldTypeError");
460
467
  var InvalidFieldTypeError = _InvalidFieldTypeError;
461
468
 
469
+ // ../../platform/api-contract/dist/index.js
470
+ var HEADER_RETRY_AFTER = { name: "Retry-After" };
471
+ var HEADER_ENTITY_TAG = { name: "X-Entity-Tag" };
472
+ var HEADER_IF_MATCH = {
473
+ name: "If-Match"};
474
+ var HEADER_IDEMPOTENCY_KEY = {
475
+ name: "Idempotency-Key"};
476
+
462
477
  // ../../platform/api-contract/dist/client/index.js
463
478
  var _a;
464
479
  var ContractBodyError = (_a = class extends Error {
@@ -502,19 +517,47 @@ function readError(body) {
502
517
  }
503
518
  chunkMWMSFIWN_cjs.__name(readError, "readError");
504
519
 
520
+ // src/http/retry-after.ts
521
+ function headerValue(headers, name) {
522
+ if (headers === void 0) return void 0;
523
+ const wanted = name.toLowerCase();
524
+ for (const [key, value] of Object.entries(headers)) {
525
+ if (key.toLowerCase() === wanted) return value;
526
+ }
527
+ return void 0;
528
+ }
529
+ chunkMWMSFIWN_cjs.__name(headerValue, "headerValue");
530
+ function parseRetryAfterMs(value, nowMs) {
531
+ if (value === void 0) return void 0;
532
+ const trimmed = value.trim();
533
+ if (trimmed === "") return void 0;
534
+ if (/^\d+$/.test(trimmed)) {
535
+ return Number(trimmed) * 1e3;
536
+ }
537
+ if (!/[A-Za-z]/.test(trimmed)) return void 0;
538
+ const at = Date.parse(trimmed);
539
+ if (Number.isNaN(at)) return void 0;
540
+ return Math.max(0, at - nowMs);
541
+ }
542
+ chunkMWMSFIWN_cjs.__name(parseRetryAfterMs, "parseRetryAfterMs");
543
+
505
544
  // src/errors/error-mapper.ts
506
545
  var _ErrorMapper = class _ErrorMapper {
507
546
  /** Map a non-2xx response to an SDK error. */
508
547
  static mapHttpError(response, requestContext) {
509
- const { status, statusText, body } = response;
548
+ const { status, statusText, body, headers } = response;
510
549
  const refusal = readError(body);
511
550
  const context = { ...requestContext, response: body };
512
551
  if (refusal === void 0) {
513
552
  return new NetworkError(`HTTP ${status}: ${statusText}`.trim(), status, void 0, context);
514
553
  }
515
- return this.fromContractCode(refusal, status, context);
554
+ const retryAfterMs = parseRetryAfterMs(
555
+ headerValue(headers, HEADER_RETRY_AFTER.name),
556
+ Date.now()
557
+ );
558
+ return this.fromContractCode(refusal, status, context, retryAfterMs);
516
559
  }
517
- static fromContractCode(refusal, status, context) {
560
+ static fromContractCode(refusal, status, context, retryAfterMs) {
518
561
  const message = refusal.messages[0] ?? `RelyIO refused with ${refusal.code}`;
519
562
  switch (refusal.code) {
520
563
  case "MISSING_API_KEY": {
@@ -545,7 +588,8 @@ var _ErrorMapper = class _ErrorMapper {
545
588
  message,
546
589
  detailNumber(refusal, "limit"),
547
590
  detailString(refusal, "resetAt"),
548
- context
591
+ context,
592
+ retryAfterMs
549
593
  );
550
594
  }
551
595
  case "REQUEST_INVALID": {
@@ -588,7 +632,12 @@ var _ErrorMapper = class _ErrorMapper {
588
632
  );
589
633
  }
590
634
  case "SERVICE_INDETERMINATE": {
591
- return new ServiceIndeterminateError(message, detailString(refusal, "missing"), context);
635
+ return new ServiceIndeterminateError(
636
+ message,
637
+ detailString(refusal, "missing"),
638
+ context,
639
+ retryAfterMs
640
+ );
592
641
  }
593
642
  case "INTERNAL_ERROR": {
594
643
  return new ServerError(message, status, context);
@@ -630,12 +679,6 @@ var _ErrorMapper = class _ErrorMapper {
630
679
  }
631
680
  return error.name === "TypeError" && error.message.includes("fetch") || error.message.includes("timeout") || error.message.includes("Network Error") || error.message.includes("ECONNREFUSED");
632
681
  }
633
- /**
634
- * Extract retry delay from error
635
- */
636
- static getRetryDelay(_error) {
637
- return 0;
638
- }
639
682
  };
640
683
  chunkMWMSFIWN_cjs.__name(_ErrorMapper, "ErrorMapper");
641
684
  var ErrorMapper = _ErrorMapper;
@@ -694,13 +737,61 @@ function fillPath(template, params) {
694
737
  }
695
738
  chunkMWMSFIWN_cjs.__name(fillPath, "fillPath");
696
739
 
740
+ // src/http/retry-policy.ts
741
+ var IDEMPOTENT_METHODS = /* @__PURE__ */ new Set([
742
+ "GET",
743
+ "HEAD",
744
+ "OPTIONS",
745
+ "PUT",
746
+ "DELETE",
747
+ "TRACE"
748
+ ]);
749
+ function isReplaySafe(request) {
750
+ if (IDEMPOTENT_METHODS.has(request.method.toUpperCase())) return true;
751
+ return headerValue(request.headers, HEADER_IDEMPOTENCY_KEY.name) !== void 0;
752
+ }
753
+ chunkMWMSFIWN_cjs.__name(isReplaySafe, "isReplaySafe");
754
+ function serviceInstructedDelayMs(error, nowMs) {
755
+ if (error instanceof ServiceIndeterminateError) {
756
+ return error.retryAfterMs;
757
+ }
758
+ if (!(error instanceof RateLimitError)) return void 0;
759
+ if (error.retryAfterMs !== void 0) return error.retryAfterMs;
760
+ if (error.resetAt === void 0) return void 0;
761
+ const resetAtMs = Date.parse(error.resetAt);
762
+ if (Number.isNaN(resetAtMs)) return void 0;
763
+ const wait = resetAtMs - nowMs;
764
+ return wait > 0 ? wait : void 0;
765
+ }
766
+ chunkMWMSFIWN_cjs.__name(serviceInstructedDelayMs, "serviceInstructedDelayMs");
767
+ var _DefaultRetryPolicy = class _DefaultRetryPolicy {
768
+ constructor(config) {
769
+ chunkMWMSFIWN_cjs.__publicField(this, "config");
770
+ this.config = config;
771
+ }
772
+ shouldRetry(attempt, error, request) {
773
+ if (attempt >= this.config.maxAttempts) return false;
774
+ if (!ErrorMapper.isRetryable(error)) return false;
775
+ return isReplaySafe(request);
776
+ }
777
+ delayFor(attempt, error) {
778
+ const instructed = serviceInstructedDelayMs(error, Date.now());
779
+ if (instructed !== void 0) return instructed;
780
+ const exponential = this.config.baseDelay * Math.pow(this.config.backoffFactor, attempt - 1);
781
+ const delay = Math.min(exponential, this.config.maxDelay);
782
+ return this.config.jitter ? delay * (0.5 + Math.random() * 0.5) : delay;
783
+ }
784
+ };
785
+ chunkMWMSFIWN_cjs.__name(_DefaultRetryPolicy, "DefaultRetryPolicy");
786
+ var DefaultRetryPolicy = _DefaultRetryPolicy;
787
+
697
788
  // src/http/http-client.ts
698
789
  var _HttpClient = class _HttpClient {
699
790
  constructor(config) {
700
791
  chunkMWMSFIWN_cjs.__publicField(this, "baseUrl");
701
792
  chunkMWMSFIWN_cjs.__publicField(this, "defaultHeaders");
702
793
  chunkMWMSFIWN_cjs.__publicField(this, "timeout");
703
- chunkMWMSFIWN_cjs.__publicField(this, "retryConfig");
794
+ chunkMWMSFIWN_cjs.__publicField(this, "retryPolicy");
704
795
  chunkMWMSFIWN_cjs.__publicField(this, "debug");
705
796
  chunkMWMSFIWN_cjs.__publicField(this, "logger");
706
797
  chunkMWMSFIWN_cjs.__publicField(this, "config");
@@ -721,16 +812,13 @@ var _HttpClient = class _HttpClient {
721
812
  const globalWithVersion = globalThis;
722
813
  this.defaultHeaders["User-Agent"] = `RelyIO-SDK/${globalWithVersion.__VERSION__ ?? "0.0.0"}`;
723
814
  }
724
- this.retryConfig = {
815
+ this.retryPolicy = new DefaultRetryPolicy({
725
816
  maxAttempts: config.retryAttempts,
726
817
  baseDelay: 1e3,
727
818
  maxDelay: 1e4,
728
819
  backoffFactor: 2,
729
- jitter: true,
730
- retryCondition: /* @__PURE__ */ chunkMWMSFIWN_cjs.__name((error, attempt) => {
731
- return ErrorMapper.isRetryable(error) && attempt < config.retryAttempts;
732
- }, "retryCondition")
733
- };
820
+ jitter: true
821
+ });
734
822
  this.log("debug", "HttpClient initialized", {
735
823
  baseUrl: this.baseUrl,
736
824
  timeout: this.timeout,
@@ -763,7 +851,16 @@ var _HttpClient = class _HttpClient {
763
851
  return readPayload(response.body);
764
852
  }
765
853
  /**
766
- * Make an HTTP request with retry logic
854
+ * Make an HTTP request, retrying only where a retry is safe.
855
+ *
856
+ * The headers are merged **once**, before the first attempt, and every attempt
857
+ * sends that same object. That is what makes a minted `Idempotency-Key` stable
858
+ * across the retries of one logical write: the service sees one key and
859
+ * answers the replay from the original outcome instead of applying it twice.
860
+ *
861
+ * The loop always makes one attempt. The incumbent's `while (attempt <
862
+ * maxAttempts)` made none at `retryAttempts: 0` — a value the config validator
863
+ * accepts — and then dereferenced an unassigned `lastError`.
767
864
  */
768
865
  async request(config) {
769
866
  const mergedConfig = this.mergeRequestConfig(config);
@@ -772,14 +869,11 @@ var _HttpClient = class _HttpClient {
772
869
  url: mergedConfig.url
773
870
  // Show the final URL with query params
774
871
  });
775
- let lastError;
776
- let attempt = 0;
777
- while (attempt < this.retryConfig.maxAttempts) {
778
- attempt++;
872
+ if (config.method === "DELETE" && mergedConfig.headers) {
873
+ delete mergedConfig.headers["Content-Type"];
874
+ }
875
+ for (let attempt = 1; ; attempt++) {
779
876
  try {
780
- if (config.method === "DELETE" && mergedConfig.headers) {
781
- delete mergedConfig.headers["Content-Type"];
782
- }
783
877
  const response = await this.makeRequest(mergedConfig.url, mergedConfig);
784
878
  this.log("debug", "Request successful", {
785
879
  status: response.status,
@@ -788,31 +882,30 @@ var _HttpClient = class _HttpClient {
788
882
  });
789
883
  return response;
790
884
  } catch (error) {
791
- lastError = error;
885
+ const failure = error;
792
886
  this.log("warn", "Request failed", {
793
887
  error: error instanceof Error ? error.message : "Unknown error",
794
888
  attempt,
795
889
  url: mergedConfig.url
796
890
  });
797
- if (attempt < this.retryConfig.maxAttempts && this.retryConfig.retryCondition?.(lastError, attempt) === true) {
798
- const delay = this.calculateRetryDelay(attempt);
891
+ if (this.retryPolicy.shouldRetry(attempt, failure, mergedConfig)) {
892
+ const delay = this.retryPolicy.delayFor(attempt, failure);
799
893
  this.log("debug", "Retrying request", { attempt, delay });
800
894
  await this.sleep(delay);
801
895
  continue;
802
896
  }
803
- break;
897
+ const sdkError = ErrorMapper.mapGenericError(failure, {
898
+ url: mergedConfig.url,
899
+ method: mergedConfig.method,
900
+ attempt
901
+ });
902
+ this.log("error", "Request failed after all retries", {
903
+ error: sdkError.toJSON(),
904
+ url: mergedConfig.url
905
+ });
906
+ throw sdkError;
804
907
  }
805
908
  }
806
- const sdkError = ErrorMapper.mapGenericError(lastError, {
807
- url: mergedConfig.url,
808
- method: mergedConfig.method,
809
- attempt
810
- });
811
- this.log("error", "Request failed after all retries", {
812
- error: sdkError.toJSON(),
813
- url: mergedConfig.url
814
- });
815
- throw sdkError;
816
909
  }
817
910
  /**
818
911
  * Make a GET request
@@ -1060,14 +1153,6 @@ var _HttpClient = class _HttpClient {
1060
1153
  });
1061
1154
  return obj;
1062
1155
  }
1063
- calculateRetryDelay(attempt) {
1064
- const exponentialDelay = this.retryConfig.baseDelay * Math.pow(this.retryConfig.backoffFactor, attempt - 1);
1065
- const delay = Math.min(exponentialDelay, this.retryConfig.maxDelay);
1066
- if (this.retryConfig.jitter) {
1067
- return delay * (0.5 + Math.random() * 0.5);
1068
- }
1069
- return delay;
1070
- }
1071
1156
  sleep(ms) {
1072
1157
  return new Promise((resolve) => setTimeout(resolve, ms));
1073
1158
  }
@@ -1563,32 +1648,27 @@ var _ContentByIdBuilder = class _ContentByIdBuilder {
1563
1648
  chunkMWMSFIWN_cjs.__name(_ContentByIdBuilder, "ContentByIdBuilder");
1564
1649
  var ContentByIdBuilder = _ContentByIdBuilder;
1565
1650
 
1566
- // ../../platform/api-contract/dist/index.js
1567
- var HEADER_ENTITY_TAG = { name: "X-Entity-Tag" };
1568
- var HEADER_IF_MATCH = {
1569
- name: "If-Match"};
1570
- var HEADER_IDEMPOTENCY_KEY = {
1571
- name: "Idempotency-Key"};
1572
-
1573
1651
  // src/http/write-options.ts
1652
+ function mintIdempotencyKey() {
1653
+ const webCrypto = globalThis.crypto;
1654
+ if (typeof webCrypto?.randomUUID !== "function") return void 0;
1655
+ return webCrypto.randomUUID();
1656
+ }
1657
+ chunkMWMSFIWN_cjs.__name(mintIdempotencyKey, "mintIdempotencyKey");
1574
1658
  function writeHeaders(options) {
1575
- if (options === void 0) return void 0;
1576
1659
  const headers = {};
1577
- if ("ifMatch" in options && options.ifMatch !== void 0) {
1660
+ if (options !== void 0 && "ifMatch" in options && options.ifMatch !== void 0) {
1578
1661
  headers[HEADER_IF_MATCH.name] = options.ifMatch;
1579
1662
  }
1580
- if (options.idempotencyKey !== void 0) {
1581
- headers[HEADER_IDEMPOTENCY_KEY.name] = options.idempotencyKey;
1663
+ const idempotencyKey = options?.idempotencyKey ?? mintIdempotencyKey();
1664
+ if (idempotencyKey !== void 0) {
1665
+ headers[HEADER_IDEMPOTENCY_KEY.name] = idempotencyKey;
1582
1666
  }
1583
1667
  return Object.keys(headers).length > 0 ? headers : void 0;
1584
1668
  }
1585
1669
  chunkMWMSFIWN_cjs.__name(writeHeaders, "writeHeaders");
1586
1670
  function entityTagOf(response) {
1587
- const wanted = HEADER_ENTITY_TAG.name.toLowerCase();
1588
- for (const [name, value] of Object.entries(response.headers)) {
1589
- if (name.toLowerCase() === wanted) return value;
1590
- }
1591
- return void 0;
1671
+ return headerValue(response.headers, HEADER_ENTITY_TAG.name);
1592
1672
  }
1593
1673
  chunkMWMSFIWN_cjs.__name(entityTagOf, "entityTagOf");
1594
1674
 
@@ -3872,47 +3952,76 @@ function isProduction(proc) {
3872
3952
  return proc?.env?.NODE_ENV === "production";
3873
3953
  }
3874
3954
  chunkMWMSFIWN_cjs.__name(isProduction, "isProduction");
3875
- function resolveFromEnv(proc) {
3876
- const dataset = proc?.env?.RELYIO_DEV_DATASET;
3877
- const apiKey = proc?.env?.RELYIO_DEV_API_KEY;
3878
- if (dataset != null && dataset !== "" && apiKey != null && apiKey !== "") {
3879
- return { dataset, apiKey, source: "env" };
3955
+ function resolveFromEnv(proc, now) {
3956
+ const verdict = chunkLSRPMNPT_cjs.parseDevEnv(proc?.env, now);
3957
+ switch (verdict.state) {
3958
+ case "active":
3959
+ return {
3960
+ state: "active",
3961
+ override: {
3962
+ dataset: verdict.credentials.dataset,
3963
+ apiKey: verdict.credentials.apiKey,
3964
+ source: "env"
3965
+ }
3966
+ };
3967
+ case "expired":
3968
+ return { state: "expired", expiredAt: verdict.expiredAt };
3969
+ case "unreadable":
3970
+ return { state: "unreadable", reason: verdict.reason };
3971
+ case "absent":
3972
+ return { state: "absent" };
3880
3973
  }
3881
- return null;
3882
3974
  }
3883
3975
  chunkMWMSFIWN_cjs.__name(resolveFromEnv, "resolveFromEnv");
3884
- function resolveFromFile(proc) {
3885
- if (proc == null || typeof proc.cwd !== "function") return null;
3976
+ function resolveFromFile(proc, now) {
3977
+ if (proc == null || typeof proc.cwd !== "function") return { state: "absent" };
3886
3978
  const fs = getBuiltin(proc, "node:fs");
3887
3979
  const path = getBuiltin(proc, "node:path");
3888
- if (fs == null || path == null) return null;
3980
+ if (fs == null || path == null) return { state: "absent" };
3889
3981
  let dir = proc.cwd();
3890
3982
  for (; ; ) {
3891
- const candidate = path.join(dir, ".relyio", "dev.json");
3983
+ const candidate = path.join(dir, chunkLSRPMNPT_cjs.DEV_CONTEXT_DIR, chunkLSRPMNPT_cjs.DEV_CONTEXT_FILENAME);
3984
+ let raw = null;
3892
3985
  try {
3893
- if (fs.existsSync(candidate)) {
3894
- const parsed = JSON.parse(fs.readFileSync(candidate, "utf8"));
3895
- if (parsed.datasetId != null && parsed.datasetId !== "" && parsed.previewApiKey != null && parsed.previewApiKey !== "") {
3896
- if (parsed.expiresAt != null && Number.isFinite(new Date(parsed.expiresAt).getTime()) && new Date(parsed.expiresAt).getTime() < Date.now()) {
3897
- return null;
3898
- }
3899
- return { dataset: parsed.datasetId, apiKey: parsed.previewApiKey, source: "file" };
3900
- }
3901
- }
3986
+ if (fs.existsSync(candidate)) raw = fs.readFileSync(candidate, "utf8");
3902
3987
  } catch {
3988
+ return { state: "absent" };
3989
+ }
3990
+ if (raw !== null) {
3991
+ const verdict = chunkLSRPMNPT_cjs.parseDevContextFile(raw, now);
3992
+ switch (verdict.state) {
3993
+ case "active":
3994
+ return {
3995
+ state: "active",
3996
+ override: {
3997
+ dataset: verdict.context.datasetId,
3998
+ apiKey: verdict.context.previewApiKey,
3999
+ source: "file"
4000
+ }
4001
+ };
4002
+ case "expired":
4003
+ return { state: "expired", expiredAt: verdict.expiredAt };
4004
+ case "unreadable":
4005
+ return { state: "unreadable", reason: verdict.reason };
4006
+ case "absent":
4007
+ return { state: "absent" };
4008
+ }
3903
4009
  }
3904
4010
  const parent = path.dirname(dir);
3905
4011
  if (parent === dir) break;
3906
4012
  dir = parent;
3907
4013
  }
3908
- return null;
4014
+ return { state: "absent" };
3909
4015
  }
3910
4016
  chunkMWMSFIWN_cjs.__name(resolveFromFile, "resolveFromFile");
3911
4017
  function resolveDevOverride(dev) {
3912
- if (dev === false) return null;
4018
+ if (dev === false) return { state: "disabled" };
3913
4019
  const proc = getProcess();
3914
- if (dev !== true && isProduction(proc)) return null;
3915
- return resolveFromEnv(proc) ?? resolveFromFile(proc);
4020
+ if (dev !== true && isProduction(proc)) return { state: "disabled" };
4021
+ const now = Date.now();
4022
+ const fromEnv = resolveFromEnv(proc, now);
4023
+ if (fromEnv.state !== "absent") return fromEnv;
4024
+ return resolveFromFile(proc, now);
3916
4025
  }
3917
4026
  chunkMWMSFIWN_cjs.__name(resolveDevOverride, "resolveDevOverride");
3918
4027
 
@@ -3995,16 +4104,26 @@ function mergeConfig(userConfig) {
3995
4104
  ...userConfig.debug
3996
4105
  }
3997
4106
  };
3998
- const devOverride = resolveDevOverride(userConfig.dev);
3999
- if (devOverride != null) {
4000
- merged.apiKey = devOverride.apiKey;
4001
- merged.dataset = devOverride.dataset;
4107
+ const dev = resolveDevOverride(userConfig.dev);
4108
+ if (dev.state === "active") {
4109
+ merged.apiKey = dev.override.apiKey;
4110
+ merged.dataset = dev.override.dataset;
4002
4111
  if (!devOverrideNotified) {
4003
4112
  devOverrideNotified = true;
4004
4113
  console.info(
4005
- `[RelyIO] Sandbox dev mode active (${devOverride.source}) \u2014 targeting dataset "${devOverride.dataset}" with a scoped preview key.`
4114
+ `[RelyIO] Sandbox dev mode active (${dev.override.source}) \u2014 targeting dataset "${dev.override.dataset}" with a scoped preview key.`
4006
4115
  );
4007
4116
  }
4117
+ } else if (dev.state === "expired" && !devOverrideNotified) {
4118
+ devOverrideNotified = true;
4119
+ console.warn(
4120
+ `[RelyIO] Your sandbox's .relyio/dev.json expired at ${dev.expiredAt} \u2014 ignoring it and using the configured dataset "${merged.dataset}". Run \`relyio dev\` to refresh it.`
4121
+ );
4122
+ } else if (dev.state === "unreadable" && !devOverrideNotified) {
4123
+ devOverrideNotified = true;
4124
+ console.warn(
4125
+ `[RelyIO] Your .relyio/dev.json could not be read (${dev.reason}) \u2014 ignoring it and using the configured dataset "${merged.dataset}". Run \`relyio dev\` to rewrite it.`
4126
+ );
4008
4127
  }
4009
4128
  return merged;
4010
4129
  }
@@ -4425,5 +4544,5 @@ exports.entityTagOf = entityTagOf;
4425
4544
  exports.isAssetEntry = isAssetEntry;
4426
4545
  exports.isResourceNotFound = isResourceNotFound;
4427
4546
  exports.validateFile = validateFile;
4428
- //# sourceMappingURL=chunk-7W4MJXQ4.cjs.map
4429
- //# sourceMappingURL=chunk-7W4MJXQ4.cjs.map
4547
+ //# sourceMappingURL=chunk-54WP2PWG.cjs.map
4548
+ //# sourceMappingURL=chunk-54WP2PWG.cjs.map
@@ -0,0 +1,81 @@
1
+ import { __name } from './chunk-XQ4QHWZE.js';
2
+
3
+ /* RelyIO SDK - Built with ❤️ by the RelyIO Team */
4
+
5
+ // src/dev-handshake/index.ts
6
+ var DEV_CONTEXT_DIR = ".relyio";
7
+ var DEV_CONTEXT_FILENAME = "dev.json";
8
+ var DEV_CONTEXT_FILE = `${DEV_CONTEXT_DIR}/${DEV_CONTEXT_FILENAME}`;
9
+ var DEV_ENV = {
10
+ dataset: "RELYIO_DEV_DATASET",
11
+ apiKey: "RELYIO_DEV_API_KEY",
12
+ expiresAt: "RELYIO_DEV_EXPIRES_AT"
13
+ };
14
+ function readDeadline(raw, now) {
15
+ if (raw === void 0 || raw === null || raw === "") return { state: "none" };
16
+ if (typeof raw !== "string") return { state: "unreadable" };
17
+ const deadline = new Date(raw).getTime();
18
+ if (!Number.isFinite(deadline)) return { state: "unreadable" };
19
+ return deadline <= now ? { state: "passed", expiredAt: raw } : { state: "live", expiresAt: raw };
20
+ }
21
+ __name(readDeadline, "readDeadline");
22
+ function parseDevContextFile(raw, now) {
23
+ let parsed;
24
+ try {
25
+ parsed = JSON.parse(raw);
26
+ } catch {
27
+ return { state: "unreadable", reason: "not-json" };
28
+ }
29
+ if (typeof parsed !== "object" || parsed === null) {
30
+ return { state: "unreadable", reason: "not-json" };
31
+ }
32
+ const record = parsed;
33
+ const { datasetId, previewApiKey, branchId, expiresAt } = record;
34
+ if (typeof datasetId !== "string" || datasetId === "" || typeof previewApiKey !== "string" || previewApiKey === "" || typeof branchId !== "string" || branchId === "") {
35
+ return { state: "unreadable", reason: "missing-fields" };
36
+ }
37
+ const deadline = readDeadline(expiresAt, now);
38
+ if (deadline.state === "unreadable") return { state: "unreadable", reason: "bad-expiry" };
39
+ const context = {
40
+ datasetId,
41
+ previewApiKey,
42
+ branchId,
43
+ expiresAt: deadline.state === "none" ? null : expiresAt
44
+ };
45
+ return deadline.state === "passed" ? { state: "expired", context, expiredAt: deadline.expiredAt } : { state: "active", context };
46
+ }
47
+ __name(parseDevContextFile, "parseDevContextFile");
48
+ function buildDevEnv(context) {
49
+ const env = {
50
+ [DEV_ENV.dataset]: context.datasetId,
51
+ [DEV_ENV.apiKey]: context.previewApiKey
52
+ };
53
+ if (context.expiresAt != null && context.expiresAt !== "") {
54
+ env[DEV_ENV.expiresAt] = context.expiresAt;
55
+ }
56
+ return env;
57
+ }
58
+ __name(buildDevEnv, "buildDevEnv");
59
+ function parseDevEnv(env, now) {
60
+ const dataset = env?.[DEV_ENV.dataset];
61
+ const apiKey = env?.[DEV_ENV.apiKey];
62
+ if (dataset == null || dataset === "" || apiKey == null || apiKey === "") {
63
+ return { state: "absent" };
64
+ }
65
+ const deadline = readDeadline(env?.[DEV_ENV.expiresAt], now);
66
+ switch (deadline.state) {
67
+ case "unreadable":
68
+ return { state: "unreadable", reason: "bad-expiry" };
69
+ case "passed":
70
+ return { state: "expired", expiredAt: deadline.expiredAt };
71
+ case "live":
72
+ return { state: "active", credentials: { dataset, apiKey }, expiresAt: deadline.expiresAt };
73
+ case "none":
74
+ return { state: "active", credentials: { dataset, apiKey }, expiresAt: null };
75
+ }
76
+ }
77
+ __name(parseDevEnv, "parseDevEnv");
78
+
79
+ export { DEV_CONTEXT_DIR, DEV_CONTEXT_FILE, DEV_CONTEXT_FILENAME, DEV_ENV, buildDevEnv, parseDevContextFile, parseDevEnv };
80
+ //# sourceMappingURL=chunk-BBUB7C2Z.js.map
81
+ //# sourceMappingURL=chunk-BBUB7C2Z.js.map