@relyio/sdk 0.5.0 → 0.7.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 (37) hide show
  1. package/CHANGELOG.md +540 -0
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/{chunk-HAQELDZN.cjs → chunk-72UK5U7T.cjs} +70 -60
  4. package/dist/{chunk-5AZUUV5B.js → chunk-GYHAVYND.js} +1086 -494
  5. package/dist/{chunk-I3ESNANB.js → chunk-H6IBQSMM.js} +2 -2
  6. package/dist/{chunk-HT6NFRM2.cjs → chunk-TZL374LS.cjs} +2 -2
  7. package/dist/{chunk-CIRT6TVT.cjs → chunk-VH2DE7HZ.cjs} +1088 -497
  8. package/dist/{chunk-XY5KFEFY.js → chunk-YEJUMW4X.js} +70 -60
  9. package/dist/client/index.cjs +4 -3
  10. package/dist/client/index.d.cts +4 -4
  11. package/dist/client/index.d.ts +4 -4
  12. package/dist/client/index.js +2 -1
  13. package/dist/{index-C5mRBb6t.d.cts → define-content-type-DiVpbwNs.d.cts} +1629 -665
  14. package/dist/{index-mXUx2H99.d.ts → define-content-type-DiVpbwNs.d.ts} +1629 -665
  15. package/dist/{define-schema-DzYXk4HL.d.ts → define-schema-C-5an28C.d.ts} +211 -429
  16. package/dist/{define-schema-2xbHgtK-.d.cts → define-schema-D7MButVt.d.cts} +211 -429
  17. package/dist/{index-Dzo-upNS.d.ts → index-1G97sNA8.d.ts} +677 -296
  18. package/dist/index-BOQIHnsh.d.ts +818 -0
  19. package/dist/index-DbsTUw3Q.d.cts +818 -0
  20. package/dist/{index-BozPz4n1.d.cts → index-M0X45k7J.d.cts} +677 -296
  21. package/dist/index.cjs +180 -103
  22. package/dist/index.d.cts +307 -32
  23. package/dist/index.d.ts +307 -32
  24. package/dist/index.js +95 -8
  25. package/dist/schema/index.cjs +35 -35
  26. package/dist/schema/index.d.cts +21 -15
  27. package/dist/schema/index.d.ts +21 -15
  28. package/dist/schema/index.js +1 -1
  29. package/dist/types/index.d.cts +2 -2
  30. package/dist/types/index.d.ts +2 -2
  31. package/dist/utils/index.cjs +19 -19
  32. package/dist/utils/index.d.cts +10 -1
  33. package/dist/utils/index.d.ts +10 -1
  34. package/dist/utils/index.js +1 -1
  35. package/package.json +4 -3
  36. package/dist/define-content-type-CBfnTWSS.d.cts +0 -716
  37. package/dist/define-content-type-CBfnTWSS.d.ts +0 -716
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var chunkLSRPMNPT_cjs = require('./chunk-LSRPMNPT.cjs');
4
+ var chunkTZL374LS_cjs = require('./chunk-TZL374LS.cjs');
4
5
  var chunkMWMSFIWN_cjs = require('./chunk-MWMSFIWN.cjs');
5
6
 
6
7
  /* RelyIO SDK - Built with ❤️ by the RelyIO Team */
@@ -71,44 +72,6 @@ var _SDKError = class _SDKError extends Error {
71
72
  chunkMWMSFIWN_cjs.__name(_SDKError, "SDKError");
72
73
  var SDKError = _SDKError;
73
74
 
74
- // src/errors/auth-errors.ts
75
- var _AuthenticationError = class _AuthenticationError extends SDKError {
76
- constructor(message = "Authentication failed", statusCode = 401, cause, context) {
77
- super(message, statusCode, cause, context);
78
- chunkMWMSFIWN_cjs.__publicField(this, "code", "AUTHENTICATION_ERROR");
79
- chunkMWMSFIWN_cjs.__publicField(this, "retryable", false);
80
- }
81
- getUserMessage() {
82
- return "Authentication failed. Please check your API key and try again.";
83
- }
84
- };
85
- chunkMWMSFIWN_cjs.__name(_AuthenticationError, "AuthenticationError");
86
- var AuthenticationError = _AuthenticationError;
87
- var _AuthorizationError = class _AuthorizationError extends SDKError {
88
- constructor(message = "Authorization failed", statusCode = 403, cause, context) {
89
- super(message, statusCode, cause, context);
90
- chunkMWMSFIWN_cjs.__publicField(this, "code", "AUTHORIZATION_ERROR");
91
- chunkMWMSFIWN_cjs.__publicField(this, "retryable", false);
92
- }
93
- getUserMessage() {
94
- return "You do not have permission to access this resource.";
95
- }
96
- };
97
- chunkMWMSFIWN_cjs.__name(_AuthorizationError, "AuthorizationError");
98
- var AuthorizationError = _AuthorizationError;
99
- var _InvalidApiKeyError = class _InvalidApiKeyError extends SDKError {
100
- constructor(message = "Invalid API key", cause, context) {
101
- super(message, 401, cause, context);
102
- chunkMWMSFIWN_cjs.__publicField(this, "code", "INVALID_API_KEY");
103
- chunkMWMSFIWN_cjs.__publicField(this, "retryable", false);
104
- }
105
- getUserMessage() {
106
- return "Invalid API key. Please check your API key and try again.";
107
- }
108
- };
109
- chunkMWMSFIWN_cjs.__name(_InvalidApiKeyError, "InvalidApiKeyError");
110
- var InvalidApiKeyError = _InvalidApiKeyError;
111
-
112
75
  // src/errors/contract-errors.ts
113
76
  var _MissingApiKeyError = class _MissingApiKeyError extends SDKError {
114
77
  constructor(message = "No API key was sent", context) {
@@ -269,6 +232,32 @@ var _UnknownContractError = class _UnknownContractError extends SDKError {
269
232
  chunkMWMSFIWN_cjs.__name(_UnknownContractError, "UnknownContractError");
270
233
  var UnknownContractError = _UnknownContractError;
271
234
 
235
+ // src/errors/auth-errors.ts
236
+ var _AuthorizationError = class _AuthorizationError extends SDKError {
237
+ constructor(message = "Authorization failed", statusCode = 403, cause, context) {
238
+ super(message, statusCode, cause, context);
239
+ chunkMWMSFIWN_cjs.__publicField(this, "code", "AUTHORIZATION_ERROR");
240
+ chunkMWMSFIWN_cjs.__publicField(this, "retryable", false);
241
+ }
242
+ getUserMessage() {
243
+ return "You do not have permission to access this resource.";
244
+ }
245
+ };
246
+ chunkMWMSFIWN_cjs.__name(_AuthorizationError, "AuthorizationError");
247
+ var AuthorizationError = _AuthorizationError;
248
+ var _InvalidApiKeyError = class _InvalidApiKeyError extends SDKError {
249
+ constructor(message = "Invalid API key", cause, context) {
250
+ super(message, 401, cause, context);
251
+ chunkMWMSFIWN_cjs.__publicField(this, "code", "INVALID_API_KEY");
252
+ chunkMWMSFIWN_cjs.__publicField(this, "retryable", false);
253
+ }
254
+ getUserMessage() {
255
+ return "Invalid API key. Please check your API key and try again.";
256
+ }
257
+ };
258
+ chunkMWMSFIWN_cjs.__name(_InvalidApiKeyError, "InvalidApiKeyError");
259
+ var InvalidApiKeyError = _InvalidApiKeyError;
260
+
272
261
  // src/errors/content-errors.ts
273
262
  var _ResourceNotFoundError = class _ResourceNotFoundError extends SDKError {
274
263
  constructor(message = "Resource not found", statusCode = 404, cause, context) {
@@ -330,8 +319,12 @@ var _NetworkError = class _NetworkError extends SDKError {
330
319
  chunkMWMSFIWN_cjs.__name(_NetworkError, "NetworkError");
331
320
  var NetworkError = _NetworkError;
332
321
  var _TimeoutError = class _TimeoutError extends SDKError {
333
- constructor(message = "Request timed out", timeout, statusCode, cause) {
334
- super(message, statusCode, cause, { timeout });
322
+ /**
323
+ * @param context - Carried beside `timeout`. The mapper passes a `REQUEST_TIMEOUT` refusal's body
324
+ * as `context.response`, as it does for every other contract code.
325
+ */
326
+ constructor(message = "Request timed out", timeout, statusCode, cause, context) {
327
+ super(message, statusCode, cause, { ...context, timeout });
335
328
  chunkMWMSFIWN_cjs.__publicField(this, "code", "TIMEOUT_ERROR");
336
329
  chunkMWMSFIWN_cjs.__publicField(this, "retryable", true);
337
330
  }
@@ -357,8 +350,13 @@ var ConnectionError = _ConnectionError;
357
350
 
358
351
  // src/errors/validation-errors.ts
359
352
  var _BaseValidationError = class _BaseValidationError extends SDKError {
360
- constructor(message = "Validation failed", violations = [], statusCode = 400, cause) {
361
- super(message, statusCode, cause, { violations });
353
+ /**
354
+ * @param context - Carried beside `violations`. The mapper passes the refusal body as
355
+ * `context.response`, as it does for every other contract code, so a consumer that hands the
356
+ * service's own words on can read them here too.
357
+ */
358
+ constructor(message = "Validation failed", violations = [], statusCode = 400, cause, context) {
359
+ super(message, statusCode, cause, { ...context, violations });
362
360
  chunkMWMSFIWN_cjs.__publicField(this, "retryable", false);
363
361
  chunkMWMSFIWN_cjs.__publicField(this, "violations");
364
362
  this.violations = violations;
@@ -403,8 +401,8 @@ var _ValidationError = class _ValidationError extends BaseValidationError {
403
401
  chunkMWMSFIWN_cjs.__name(_ValidationError, "ValidationError");
404
402
  var ValidationError = _ValidationError;
405
403
  var _InvalidPayloadError = class _InvalidPayloadError extends BaseValidationError {
406
- constructor(message = "Invalid payload", violations = [], cause) {
407
- super(message, violations, 400, cause);
404
+ constructor(message = "Invalid payload", violations = [], cause, context) {
405
+ super(message, violations, 400, cause, context);
408
406
  chunkMWMSFIWN_cjs.__publicField(this, "code", "INVALID_PAYLOAD");
409
407
  }
410
408
  getUserMessage() {
@@ -413,58 +411,6 @@ var _InvalidPayloadError = class _InvalidPayloadError extends BaseValidationErro
413
411
  };
414
412
  chunkMWMSFIWN_cjs.__name(_InvalidPayloadError, "InvalidPayloadError");
415
413
  var InvalidPayloadError = _InvalidPayloadError;
416
- var _SchemaValidationError = class _SchemaValidationError extends BaseValidationError {
417
- constructor(message = "Schema validation failed", violations = [], cause) {
418
- super(message, violations, 400, cause);
419
- chunkMWMSFIWN_cjs.__publicField(this, "code", "SCHEMA_VALIDATION_ERROR");
420
- }
421
- getUserMessage() {
422
- return "The data does not match the expected schema. Please check your content structure.";
423
- }
424
- };
425
- chunkMWMSFIWN_cjs.__name(_SchemaValidationError, "SchemaValidationError");
426
- var SchemaValidationError = _SchemaValidationError;
427
- var _RequiredFieldError = class _RequiredFieldError extends BaseValidationError {
428
- constructor(fieldName, message, cause) {
429
- const violation = {
430
- field: fieldName,
431
- message: message ?? `Field "${fieldName}" is required`,
432
- code: "REQUIRED"
433
- };
434
- super(`Required field "${fieldName}" is missing`, [violation], 400, cause);
435
- chunkMWMSFIWN_cjs.__publicField(this, "code", "REQUIRED_FIELD_ERROR");
436
- }
437
- getUserMessage() {
438
- const violation = this.violations[0];
439
- const fieldName = violation?.field;
440
- return `The field "${fieldName}" is required and cannot be empty.`;
441
- }
442
- };
443
- chunkMWMSFIWN_cjs.__name(_RequiredFieldError, "RequiredFieldError");
444
- var RequiredFieldError = _RequiredFieldError;
445
- var _InvalidFieldTypeError = class _InvalidFieldTypeError extends BaseValidationError {
446
- constructor(fieldName, expectedType, actualType, value, cause) {
447
- const violation = {
448
- field: fieldName,
449
- message: `Expected ${expectedType}, got ${actualType}`,
450
- code: "INVALID_TYPE",
451
- value
452
- };
453
- super(
454
- `Invalid type for field "${fieldName}": expected ${expectedType}, got ${actualType}`,
455
- [violation],
456
- 400,
457
- cause
458
- );
459
- chunkMWMSFIWN_cjs.__publicField(this, "code", "INVALID_FIELD_TYPE");
460
- }
461
- getUserMessage() {
462
- const violation = this.violations[0];
463
- return `The field "${violation?.field}" has an invalid type. ${violation?.message}.`;
464
- }
465
- };
466
- chunkMWMSFIWN_cjs.__name(_InvalidFieldTypeError, "InvalidFieldTypeError");
467
- var InvalidFieldTypeError = _InvalidFieldTypeError;
468
414
 
469
415
  // ../../platform/api-contract/dist/index.js
470
416
  var HEADER_RETRY_AFTER = { name: "Retry-After" };
@@ -593,10 +539,10 @@ var _ErrorMapper = class _ErrorMapper {
593
539
  );
594
540
  }
595
541
  case "REQUEST_INVALID": {
596
- return new InvalidPayloadError(message, violationsOf(refusal));
542
+ return new InvalidPayloadError(message, violationsOf(refusal), void 0, context);
597
543
  }
598
544
  case "CONTENT_INVALID": {
599
- return new ValidationError(message, violationsOf(refusal), 400);
545
+ return new ValidationError(message, violationsOf(refusal), 400, void 0, context);
600
546
  }
601
547
  case "METHOD_NOT_ALLOWED": {
602
548
  return new MethodNotAllowedError(message, context);
@@ -619,7 +565,7 @@ var _ErrorMapper = class _ErrorMapper {
619
565
  return new ResourceExpiredError(message, 410, void 0, context);
620
566
  }
621
567
  case "REQUEST_TIMEOUT": {
622
- return new TimeoutError(message, void 0, void 0, void 0);
568
+ return new TimeoutError(message, void 0, status, void 0, context);
623
569
  }
624
570
  case "PAYLOAD_TOO_LARGE": {
625
571
  return new PayloadTooLargeError(message, detailNumber(refusal, "limitBytes"), context);
@@ -842,20 +788,43 @@ function readQuery(searchParams) {
842
788
  }
843
789
  chunkMWMSFIWN_cjs.__name(readQuery, "readQuery");
844
790
 
845
- // src/http/retry-policy.ts
846
- var IDEMPOTENT_METHODS = /* @__PURE__ */ new Set([
847
- "GET",
848
- "HEAD",
849
- "OPTIONS",
850
- "PUT",
851
- "DELETE",
852
- "TRACE"
853
- ]);
854
- function isReplaySafe(request) {
855
- if (IDEMPOTENT_METHODS.has(request.method.toUpperCase())) return true;
856
- return headerValue(request.headers, HEADER_IDEMPOTENCY_KEY.name) !== void 0;
791
+ // src/http/abortable-sleep.ts
792
+ function sleep(ms, signal) {
793
+ return new Promise((resolve) => {
794
+ if (isAborted(signal)) {
795
+ resolve();
796
+ return;
797
+ }
798
+ const done = /* @__PURE__ */ chunkMWMSFIWN_cjs.__name(() => {
799
+ clearTimeout(timer);
800
+ signal?.removeEventListener("abort", done);
801
+ resolve();
802
+ }, "done");
803
+ const timer = setTimeout(done, ms);
804
+ signal?.addEventListener("abort", done, { once: true });
805
+ });
857
806
  }
858
- chunkMWMSFIWN_cjs.__name(isReplaySafe, "isReplaySafe");
807
+ chunkMWMSFIWN_cjs.__name(sleep, "sleep");
808
+ function isAborted(signal) {
809
+ return signal?.aborted === true;
810
+ }
811
+ chunkMWMSFIWN_cjs.__name(isAborted, "isAborted");
812
+
813
+ // src/http/request-timeout.ts
814
+ var MAX_TIMER_DELAY_MS = 2147483647;
815
+ function isTimerDelay(value) {
816
+ return typeof value === "number" && value >= 1 && value <= MAX_TIMER_DELAY_MS;
817
+ }
818
+ chunkMWMSFIWN_cjs.__name(isTimerDelay, "isTimerDelay");
819
+ function assertRequestTimeout(timeout) {
820
+ if (isTimerDelay(timeout)) return;
821
+ throw new Error(
822
+ `timeout must be a number of milliseconds from 1 to ${MAX_TIMER_DELAY_MS} \u2014 a timer given 0, NaN, Infinity or more fires at once and ends every request \u2014 and got ${String(timeout)}`
823
+ );
824
+ }
825
+ chunkMWMSFIWN_cjs.__name(assertRequestTimeout, "assertRequestTimeout");
826
+
827
+ // src/errors/instructed-delay.ts
859
828
  function serviceInstructedDelayMs(error, nowMs) {
860
829
  if (error instanceof ServiceIndeterminateError) {
861
830
  return error.retryAfterMs;
@@ -869,6 +838,21 @@ function serviceInstructedDelayMs(error, nowMs) {
869
838
  return wait > 0 ? wait : void 0;
870
839
  }
871
840
  chunkMWMSFIWN_cjs.__name(serviceInstructedDelayMs, "serviceInstructedDelayMs");
841
+
842
+ // src/http/retry-policy.ts
843
+ var IDEMPOTENT_METHODS = /* @__PURE__ */ new Set([
844
+ "GET",
845
+ "HEAD",
846
+ "OPTIONS",
847
+ "PUT",
848
+ "DELETE",
849
+ "TRACE"
850
+ ]);
851
+ function isReplaySafe(request) {
852
+ if (IDEMPOTENT_METHODS.has(request.method.toUpperCase())) return true;
853
+ return headerValue(request.headers, HEADER_IDEMPOTENCY_KEY.name) !== void 0;
854
+ }
855
+ chunkMWMSFIWN_cjs.__name(isReplaySafe, "isReplaySafe");
872
856
  var _DefaultRetryPolicy = class _DefaultRetryPolicy {
873
857
  constructor(config) {
874
858
  chunkMWMSFIWN_cjs.__publicField(this, "config");
@@ -893,42 +877,31 @@ var DefaultRetryPolicy = _DefaultRetryPolicy;
893
877
  // src/http/http-client.ts
894
878
  var _HttpClient = class _HttpClient {
895
879
  constructor(config) {
896
- chunkMWMSFIWN_cjs.__publicField(this, "baseUrl");
897
- chunkMWMSFIWN_cjs.__publicField(this, "defaultHeaders");
898
- chunkMWMSFIWN_cjs.__publicField(this, "timeout");
899
- chunkMWMSFIWN_cjs.__publicField(this, "retryPolicy");
900
- chunkMWMSFIWN_cjs.__publicField(this, "debug");
901
- chunkMWMSFIWN_cjs.__publicField(this, "logger");
902
- chunkMWMSFIWN_cjs.__publicField(this, "config");
903
- this.config = config;
904
- this.baseUrl = config.baseUrl;
905
- this.timeout = config.timeout;
906
- this.debug = config.debug.enabled ?? false;
907
- this.logger = config.debug.logger;
908
- this.defaultHeaders = {
909
- "Content-Type": "application/json",
910
- "X-API-Key": config.apiKey,
911
- ...config.headers
912
- };
913
- if (config.dataset != null) {
914
- this.defaultHeaders["X-RelyIO-Dataset"] = config.dataset;
915
- }
916
- if (typeof window === "undefined") {
917
- const globalWithVersion = globalThis;
918
- this.defaultHeaders["User-Agent"] = `RelyIO-SDK/${globalWithVersion.__VERSION__ ?? "0.0.0"}`;
919
- }
920
- this.retryPolicy = new DefaultRetryPolicy({
921
- maxAttempts: config.retryAttempts,
922
- baseDelay: 1e3,
923
- maxDelay: 1e4,
924
- backoffFactor: 2,
925
- jitter: true
926
- });
880
+ /** What every request reads off the configuration, derived by {@link configure} and nowhere else. */
881
+ chunkMWMSFIWN_cjs.__publicField(this, "connection");
882
+ this.connection = connectionFor(config);
883
+ this.announce();
884
+ }
885
+ /**
886
+ * Run on `config` from now on: every later request — whoever holds this client — is sent to its
887
+ * `baseUrl`, with its headers, key and dataset, its timeout and retries, through its `fetch`, and
888
+ * logged by its `debug` options.
889
+ *
890
+ * The client's own `updateConfig()` calls this rather than building a second client, so a
891
+ * `content()` or other sub-client taken before the update sends through the update too. Anything
892
+ * set on this client since it was last configured — `setDefaultHeaders()`, `setBaseUrl()` — is
893
+ * replaced by what `config` says.
894
+ */
895
+ configure(config) {
896
+ this.connection = connectionFor(config);
897
+ this.announce();
898
+ }
899
+ announce() {
927
900
  this.log("debug", "HttpClient initialized", {
928
- baseUrl: this.baseUrl,
929
- timeout: this.timeout,
930
- retryAttempts: config.retryAttempts,
931
- environment: typeof window === "undefined" ? "node" : "browser"
901
+ baseUrl: this.connection.baseUrl,
902
+ timeout: this.connection.timeout,
903
+ retryAttempts: this.connection.config.retryAttempts,
904
+ environment: chunkTZL374LS_cjs.isBrowser() ? "browser" : "node"
932
905
  });
933
906
  }
934
907
  /**
@@ -965,17 +938,34 @@ var _HttpClient = class _HttpClient {
965
938
  *
966
939
  * The loop always makes one attempt. The incumbent's `while (attempt <
967
940
  * maxAttempts)` made none at `retryAttempts: 0` — a value the config validator
968
- * accepts — and then dereferenced an unassigned `lastError`.
941
+ * then accepted, and now refuses — and then dereferenced an unassigned
942
+ * `lastError`. `retryAttempts` is the most attempts, the first included; an
943
+ * `HttpClient` constructed directly is not validated, so the first attempt
944
+ * stays unconditional.
945
+ *
946
+ * A request that would carry no `X-API-Key` makes none: it rejects with
947
+ * `MissingApiKeyError` before `fetch` is called, is not retried, and names
948
+ * `apiKey`. This is where a client built without a key learns it — the
949
+ * constructor accepts one so that a module-scope client can be evaluated by a
950
+ * build that holds no key (REL-1120) — and so does one whose `setApiKey` was
951
+ * given none.
969
952
  */
970
953
  async request(config) {
954
+ if (config.timeout !== void 0) assertRequestTimeout(config.timeout);
971
955
  const mergedConfig = this.mergeRequestConfig(config);
956
+ if (presentKey(headerValue(mergedConfig.headers, API_KEY_HEADER)) === void 0) {
957
+ throw new MissingApiKeyError(
958
+ "`apiKey` is missing: this client holds no API key, so the request was not sent. Pass `apiKey` to createClient \u2014 the documented setup reads it from RELYIO_API_KEY.",
959
+ { url: mergedConfig.url, method: mergedConfig.method, sent: false }
960
+ );
961
+ }
972
962
  this.log("debug", "Making request", {
973
963
  method: mergedConfig.method,
974
964
  url: mergedConfig.url
975
965
  // Show the final URL with query params
976
966
  });
977
967
  if (config.method === "DELETE" && mergedConfig.headers) {
978
- delete mergedConfig.headers["Content-Type"];
968
+ mergedConfig.headers = layerHeaders(mergedConfig.headers, { "Content-Type": null });
979
969
  }
980
970
  for (let attempt = 1; ; attempt++) {
981
971
  try {
@@ -994,10 +984,10 @@ var _HttpClient = class _HttpClient {
994
984
  attempt,
995
985
  url: mergedConfig.url
996
986
  });
997
- if (this.retryPolicy.shouldRetry(attempt, failure, mergedConfig)) {
998
- const delay = this.retryPolicy.delayFor(attempt, failure);
987
+ if (this.connection.retryPolicy.shouldRetry(attempt, failure, mergedConfig)) {
988
+ const delay = this.connection.retryPolicy.delayFor(attempt, failure);
999
989
  this.log("debug", "Retrying request", { attempt, delay });
1000
- await this.sleep(delay, config.signal);
990
+ await sleep(delay, config.signal);
1001
991
  continue;
1002
992
  }
1003
993
  const sdkError = ErrorMapper.mapGenericError(failure, {
@@ -1067,34 +1057,60 @@ var _HttpClient = class _HttpClient {
1067
1057
  });
1068
1058
  }
1069
1059
  /**
1070
- * Set default headers for all requests
1060
+ * Add headers to every later request, each replacing any header of the same name — the name
1061
+ * matched without regard to case — until the client is next configured.
1071
1062
  */
1072
1063
  setDefaultHeaders(headers) {
1073
- this.defaultHeaders = { ...this.defaultHeaders, ...headers };
1064
+ this.connection = {
1065
+ ...this.connection,
1066
+ defaultHeaders: layerHeaders(this.connection.defaultHeaders, headers)
1067
+ };
1074
1068
  }
1075
1069
  /**
1076
- * Set authentication token
1070
+ * Replace the key every later request sends as `X-API-Key`, and the `apiKey` the configuration
1071
+ * reports.
1072
+ *
1073
+ * The key is read the way construction reads it (`presentKey`): an absent, empty or blank key is
1074
+ * no key, so the header is dropped and the next request is refused with `MissingApiKeyError`
1075
+ * before anything is sent. A key header of any other spelling — one given in `headers` — goes
1076
+ * too, from the headers sent and from the headers the configuration reports, so the old key is
1077
+ * never sent beside the new one.
1078
+ *
1079
+ * The configuration is the one the owning `TypedRelyIOClient` holds (it hands its own to the
1080
+ * constructor), so its `getConfig().apiKey` — what a surface reads to decide a key's kind — is the
1081
+ * key sent from here on.
1082
+ *
1083
+ * This replaced `setAuthToken`, which added `Authorization: Bearer` beside an `X-API-Key` it
1084
+ * never touched: the API reads `X-API-Key` first, so the old key was the one used (REL-1385).
1077
1085
  */
1078
- setAuthToken(token) {
1079
- this.defaultHeaders.Authorization = `Bearer ${token}`;
1086
+ setApiKey(apiKey) {
1087
+ const { config } = this.connection;
1088
+ config.apiKey = apiKey;
1089
+ config.headers = layerHeaders(config.headers, { [API_KEY_HEADER]: null });
1090
+ this.connection = {
1091
+ ...this.connection,
1092
+ defaultHeaders: layerHeaders(this.connection.defaultHeaders, {
1093
+ [API_KEY_HEADER]: presentKey(apiKey) ?? null
1094
+ })
1095
+ };
1080
1096
  }
1081
1097
  /**
1082
1098
  * Get current default headers
1083
1099
  */
1084
1100
  getDefaultHeaders() {
1085
- return { ...this.defaultHeaders };
1101
+ return { ...this.connection.defaultHeaders };
1086
1102
  }
1087
1103
  /**
1088
1104
  * Update base URL
1089
1105
  */
1090
1106
  setBaseUrl(baseUrl) {
1091
- this.baseUrl = baseUrl;
1107
+ this.connection = { ...this.connection, baseUrl };
1092
1108
  }
1093
1109
  /**
1094
1110
  * Get current base URL
1095
1111
  */
1096
1112
  getBaseUrl() {
1097
- return this.baseUrl;
1113
+ return this.connection.baseUrl;
1098
1114
  }
1099
1115
  /**
1100
1116
  * One attempt.
@@ -1116,7 +1132,7 @@ var _HttpClient = class _HttpClient {
1116
1132
  const timeoutId = setTimeout(() => {
1117
1133
  timedOut = true;
1118
1134
  controller.abort();
1119
- }, config.timeout ?? this.timeout);
1135
+ }, config.timeout ?? this.connection.timeout);
1120
1136
  const onCallerAbort = /* @__PURE__ */ chunkMWMSFIWN_cjs.__name(() => controller.abort(), "onCallerAbort");
1121
1137
  caller?.addEventListener("abort", onCallerAbort, { once: true });
1122
1138
  try {
@@ -1126,10 +1142,15 @@ var _HttpClient = class _HttpClient {
1126
1142
  headers: filteredHeaders,
1127
1143
  body: this.prepareRequestBody(config),
1128
1144
  signal: controller.signal,
1129
- // Include credentials for CORS requests if in browser and configured
1130
- ...typeof window !== "undefined" && this.config.browser.includeCredentials != null && { credentials: "include" }
1145
+ // Only in a browser — a `window` with a `document` (`isBrowser()`). A server or edge
1146
+ // runtime has no cookie jar for the mode to govern, so the field is left off and the
1147
+ // runtime's default stands; that includes Deno 1.x, whose global is a `window` with no
1148
+ // `document`. A Web Worker gets that default too: it has cookies but no `window`.
1149
+ ...chunkTZL374LS_cjs.isBrowser() && {
1150
+ credentials: credentialsModeFor(this.connection.config.browser.includeCredentials)
1151
+ }
1131
1152
  };
1132
- const send = this.config.fetch ?? globalThis.fetch;
1153
+ const send = this.connection.config.fetch ?? globalThis.fetch;
1133
1154
  const response = await send(url, requestInit);
1134
1155
  clearTimeout(timeoutId);
1135
1156
  const responseBody = await this.parseResponse(response);
@@ -1162,10 +1183,8 @@ var _HttpClient = class _HttpClient {
1162
1183
  }
1163
1184
  if (error instanceof DOMException && error.name === "AbortError") {
1164
1185
  if (!timedOut && isAborted(caller)) throw error;
1165
- throw new TimeoutError(
1166
- `Request timed out after ${config.timeout ?? this.timeout}ms`,
1167
- config.timeout ?? this.timeout
1168
- );
1186
+ const timeout = config.timeout ?? this.connection.timeout;
1187
+ throw new TimeoutError(`Request timed out after ${timeout}ms`, timeout);
1169
1188
  }
1170
1189
  if (error instanceof TypeError && error.message.includes("CORS")) {
1171
1190
  const corsError = new NetworkError(
@@ -1179,9 +1198,10 @@ var _HttpClient = class _HttpClient {
1179
1198
  suggestion: "Check if the API server allows requests from your domain and has proper CORS headers configured."
1180
1199
  }
1181
1200
  );
1182
- if (typeof window !== "undefined" && this.config.browser.onCorsError) {
1201
+ const { onCorsError } = this.connection.config.browser;
1202
+ if (chunkTZL374LS_cjs.isBrowser() && onCorsError) {
1183
1203
  try {
1184
- this.config.browser.onCorsError(corsError, {
1204
+ onCorsError(corsError, {
1185
1205
  url: config.url,
1186
1206
  method: config.method,
1187
1207
  originalError: error
@@ -1198,20 +1218,20 @@ var _HttpClient = class _HttpClient {
1198
1218
  }
1199
1219
  }
1200
1220
  /**
1201
- * Filter headers that browsers don't allow to be set manually
1221
+ * The headers without those a browser controls itself, when this is a browser; unchanged when it
1222
+ * is not.
1223
+ *
1224
+ * A name is matched without regard to case (RFC 9110 §5.1), because a browser refuses `Origin`
1225
+ * whatever case a page spells it in. This matched only the all-lowercase and all-uppercase
1226
+ * spellings, so `Origin` and `User-Agent` — how people write them — reached `fetch` (REL-1409).
1202
1227
  */
1203
1228
  filterHeadersForBrowser(headers) {
1204
- if (typeof window !== "undefined") {
1205
- const filteredHeaders = { ...headers };
1206
- const forbiddenHeaders = ["user-agent", "referer", "origin", "host", "connection", "upgrade"];
1207
- forbiddenHeaders.forEach((header) => {
1208
- delete filteredHeaders[header];
1209
- delete filteredHeaders[header.toLowerCase()];
1210
- delete filteredHeaders[header.toUpperCase()];
1211
- });
1212
- return filteredHeaders;
1213
- }
1214
- return headers;
1229
+ if (!chunkTZL374LS_cjs.isBrowser()) return headers;
1230
+ return Object.fromEntries(
1231
+ Object.entries(headers).filter(
1232
+ ([name]) => !BROWSER_CONTROLLED_HEADERS.has(name.toLowerCase())
1233
+ )
1234
+ );
1215
1235
  }
1216
1236
  /**
1217
1237
  * Prepare request body based on content type and environment
@@ -1247,7 +1267,7 @@ var _HttpClient = class _HttpClient {
1247
1267
  if (url.startsWith("http://") || url.startsWith("https://")) {
1248
1268
  return url;
1249
1269
  }
1250
- return `${this.baseUrl}${url.startsWith("/") ? "" : "/"}${url}`;
1270
+ return `${this.connection.baseUrl}${url.startsWith("/") ? "" : "/"}${url}`;
1251
1271
  }
1252
1272
  mergeRequestConfig(config) {
1253
1273
  const url = this.resolveUrl(config.url);
@@ -1256,11 +1276,11 @@ var _HttpClient = class _HttpClient {
1256
1276
  return {
1257
1277
  ...config,
1258
1278
  url: finalUrl,
1259
- headers: {
1260
- ...this.defaultHeaders,
1261
- ...config.headers
1262
- },
1263
- timeout: config.timeout ?? this.timeout
1279
+ // The request's own headers replace the client's of the same name, whatever the case: a
1280
+ // per-request `x-relyio-dataset` sent beside the client's `X-RelyIO-Dataset` is one header
1281
+ // `Headers` joins into `main, staging`.
1282
+ headers: layerHeaders(this.connection.defaultHeaders, config.headers),
1283
+ timeout: config.timeout ?? this.connection.timeout
1264
1284
  };
1265
1285
  }
1266
1286
  buildQueryString(params) {
@@ -1283,26 +1303,11 @@ var _HttpClient = class _HttpClient {
1283
1303
  });
1284
1304
  return obj;
1285
1305
  }
1286
- /** A backoff the caller's abort cuts short; the next attempt then refuses to send. */
1287
- sleep(ms, signal) {
1288
- return new Promise((resolve) => {
1289
- if (isAborted(signal)) {
1290
- resolve();
1291
- return;
1292
- }
1293
- const done = /* @__PURE__ */ chunkMWMSFIWN_cjs.__name(() => {
1294
- clearTimeout(timer);
1295
- signal?.removeEventListener("abort", done);
1296
- resolve();
1297
- }, "done");
1298
- const timer = setTimeout(done, ms);
1299
- signal?.addEventListener("abort", done, { once: true });
1300
- });
1301
- }
1302
1306
  log(level, message, data) {
1303
- if (!this.debug) return;
1304
- if (this.logger != null) {
1305
- this.logger(level, message, data);
1307
+ const { debug } = this.connection.config;
1308
+ if (!debug.enabled) return;
1309
+ if (debug.logger != null) {
1310
+ debug.logger(level, message, data);
1306
1311
  } else {
1307
1312
  console.log(`[RelyIO SDK ${level.toUpperCase()}] ${message}`, data ?? "");
1308
1313
  }
@@ -1310,17 +1315,105 @@ var _HttpClient = class _HttpClient {
1310
1315
  };
1311
1316
  chunkMWMSFIWN_cjs.__name(_HttpClient, "HttpClient");
1312
1317
  var HttpClient = _HttpClient;
1313
- function isAborted(signal) {
1314
- return signal?.aborted === true;
1318
+ var API_KEY_HEADER = "X-API-Key";
1319
+ var DATASET_HEADER = "X-RelyIO-Dataset";
1320
+ var BROWSER_CONTROLLED_HEADERS = /* @__PURE__ */ new Set([
1321
+ "user-agent",
1322
+ "referer",
1323
+ "origin",
1324
+ "host",
1325
+ "connection",
1326
+ "upgrade"
1327
+ ]);
1328
+ function connectionFor(config) {
1329
+ const globalWithVersion = globalThis;
1330
+ const defaultHeaders = layerHeaders(
1331
+ layerHeaders({ "Content-Type": "application/json" }, config.headers),
1332
+ {
1333
+ [API_KEY_HEADER]: presentKey(config.apiKey),
1334
+ [DATASET_HEADER]: config.dataset,
1335
+ // Only outside a browser, which controls this header. "A browser" is the core's one
1336
+ // definition, `isBrowser()` — every environment question in this file asks it.
1337
+ "User-Agent": chunkTZL374LS_cjs.isBrowser() ? void 0 : `RelyIO-SDK/${globalWithVersion.__VERSION__ ?? "0.0.0"}`
1338
+ }
1339
+ );
1340
+ return {
1341
+ config,
1342
+ baseUrl: config.baseUrl,
1343
+ defaultHeaders,
1344
+ timeout: config.timeout,
1345
+ // The policy, not a predicate over `(error, attempt)`: whether a request may be sent again is a
1346
+ // property of the **route** as much as of the refusal, and a callback that never sees the
1347
+ // request cannot express that.
1348
+ retryPolicy: new DefaultRetryPolicy({
1349
+ maxAttempts: config.retryAttempts,
1350
+ baseDelay: 1e3,
1351
+ maxDelay: 1e4,
1352
+ backoffFactor: 2,
1353
+ jitter: true
1354
+ })
1355
+ };
1315
1356
  }
1316
- chunkMWMSFIWN_cjs.__name(isAborted, "isAborted");
1357
+ chunkMWMSFIWN_cjs.__name(connectionFor, "connectionFor");
1358
+ function layerHeaders(base, over) {
1359
+ const layered = { ...base };
1360
+ for (const [name, value] of Object.entries(over ?? {})) {
1361
+ if (value === void 0) continue;
1362
+ const lower = name.toLowerCase();
1363
+ for (const existing of Object.keys(layered)) {
1364
+ if (existing.toLowerCase() === lower) delete layered[existing];
1365
+ }
1366
+ if (value !== null) layered[name] = value;
1367
+ }
1368
+ return layered;
1369
+ }
1370
+ chunkMWMSFIWN_cjs.__name(layerHeaders, "layerHeaders");
1371
+ function presentKey(value) {
1372
+ return typeof value === "string" && value.trim() !== "" ? value : void 0;
1373
+ }
1374
+ chunkMWMSFIWN_cjs.__name(presentKey, "presentKey");
1375
+ function credentialsModeFor(includeCredentials) {
1376
+ return includeCredentials ? "include" : "same-origin";
1377
+ }
1378
+ chunkMWMSFIWN_cjs.__name(credentialsModeFor, "credentialsModeFor");
1379
+
1380
+ // src/content/client-locale.ts
1381
+ function callLocale(named, clientLocale) {
1382
+ if (named !== void 0) return named;
1383
+ return clientLocale === void 0 || clientLocale === "" ? void 0 : clientLocale;
1384
+ }
1385
+ chunkMWMSFIWN_cjs.__name(callLocale, "callLocale");
1386
+ function readClientLocale(locale) {
1387
+ return typeof locale === "function" ? locale : () => locale;
1388
+ }
1389
+ chunkMWMSFIWN_cjs.__name(readClientLocale, "readClientLocale");
1317
1390
 
1318
1391
  // src/content/query-builder.ts
1392
+ function namesMetadata(key) {
1393
+ return key === "_metadata" || key.startsWith("_metadata.");
1394
+ }
1395
+ chunkMWMSFIWN_cjs.__name(namesMetadata, "namesMetadata");
1396
+ function refuseMetadataConditions(conditions) {
1397
+ const named = Object.keys(conditions).filter(namesMetadata);
1398
+ if (named.length === 0) return;
1399
+ throw new ValidationError(
1400
+ `A condition cannot name ${named.join(", ")}: the API filters on the fields the content type declares, and reports _metadata beside them. Filter on a field, and read _metadata from the result.`,
1401
+ named.map((field) => ({
1402
+ field,
1403
+ code: "METADATA_CONDITION",
1404
+ message: `${field} is not a field the API can filter on`
1405
+ }))
1406
+ );
1407
+ }
1408
+ chunkMWMSFIWN_cjs.__name(refuseMetadataConditions, "refuseMetadataConditions");
1319
1409
  var _ContentQueryBuilder = class _ContentQueryBuilder {
1320
- constructor(httpClient, contentType, defaultLocale) {
1410
+ /**
1411
+ * @param locale - The client's locale (`withLocale()`, `updateConfig({ locale })`), sent when the
1412
+ * query names none: the function `TypedRelyIOClient` hands over, or a locale fixed for it
1413
+ */
1414
+ constructor(httpClient, contentType, locale) {
1321
1415
  this.httpClient = httpClient;
1322
1416
  this.contentType = contentType;
1323
- this.defaultLocale = defaultLocale;
1324
1417
  chunkMWMSFIWN_cjs.__publicField(this, "queryOptions", {});
1325
1418
  chunkMWMSFIWN_cjs.__publicField(this, "localeValue");
1326
1419
  chunkMWMSFIWN_cjs.__publicField(this, "localesArray");
@@ -1329,11 +1422,26 @@ var _ContentQueryBuilder = class _ContentQueryBuilder {
1329
1422
  chunkMWMSFIWN_cjs.__publicField(this, "childrenOptions");
1330
1423
  chunkMWMSFIWN_cjs.__publicField(this, "pageNumber");
1331
1424
  chunkMWMSFIWN_cjs.__publicField(this, "pageSizeValue");
1425
+ chunkMWMSFIWN_cjs.__publicField(this, "requestHeaders");
1426
+ /** The client's locale, asked when the query is sent rather than copied when it was built. */
1427
+ chunkMWMSFIWN_cjs.__publicField(this, "clientLocale");
1428
+ this.clientLocale = readClientLocale(locale);
1429
+ }
1430
+ /**
1431
+ * Headers this query adds to the client's own, e.g. `X-RelyIO-Dataset` to list another dataset
1432
+ * the key can see. A name the client already sends is replaced for this query only; the client
1433
+ * is not changed.
1434
+ */
1435
+ headers(headers) {
1436
+ this.requestHeaders = headers;
1437
+ return this;
1332
1438
  }
1333
1439
  /**
1334
- * Add where conditions to the query
1440
+ * Add where conditions to the query: conditions on the fields the content type declares. A
1441
+ * condition on `_metadata` is refused here, before anything is sent (`refuseMetadataConditions`).
1335
1442
  */
1336
1443
  where(conditions) {
1444
+ refuseMetadataConditions(conditions);
1337
1445
  this.queryOptions.where = {
1338
1446
  ...this.queryOptions.where,
1339
1447
  ...conditions
@@ -1526,7 +1634,11 @@ var _ContentQueryBuilder = class _ContentQueryBuilder {
1526
1634
  const response = await this.httpClient.send(
1527
1635
  "/v1/{contentType}",
1528
1636
  "get",
1529
- { path: { contentType: this.contentType }, query: this.buildQueryParams() }
1637
+ {
1638
+ path: { contentType: this.contentType },
1639
+ query: this.buildQueryParams(),
1640
+ headers: this.requestHeaders
1641
+ }
1530
1642
  );
1531
1643
  return response.body;
1532
1644
  }
@@ -1586,7 +1698,11 @@ var _ContentQueryBuilder = class _ContentQueryBuilder {
1586
1698
  * Clone the query builder
1587
1699
  */
1588
1700
  clone() {
1589
- const cloned = new _ContentQueryBuilder(this.httpClient, this.contentType, this.defaultLocale);
1701
+ const cloned = new _ContentQueryBuilder(
1702
+ this.httpClient,
1703
+ this.contentType,
1704
+ this.clientLocale
1705
+ );
1590
1706
  cloned.queryOptions = JSON.parse(JSON.stringify(this.queryOptions));
1591
1707
  cloned.localeValue = this.localeValue;
1592
1708
  cloned.localesArray = this.localesArray ? [...this.localesArray] : void 0;
@@ -1594,6 +1710,7 @@ var _ContentQueryBuilder = class _ContentQueryBuilder {
1594
1710
  cloned.fieldsProjection = this.fieldsProjection ? [...this.fieldsProjection] : void 0;
1595
1711
  cloned.pageNumber = this.pageNumber;
1596
1712
  cloned.pageSizeValue = this.pageSizeValue;
1713
+ cloned.requestHeaders = this.requestHeaders ? { ...this.requestHeaders } : void 0;
1597
1714
  return cloned;
1598
1715
  }
1599
1716
  /**
@@ -1660,14 +1777,15 @@ var _ContentQueryBuilder = class _ContentQueryBuilder {
1660
1777
  if (this.queryOptions.cursor !== void 0) {
1661
1778
  params.cursor = this.queryOptions.cursor;
1662
1779
  }
1780
+ const clientLocale = this.clientLocale();
1663
1781
  if (this.localeValue != null && this.localeValue !== "") {
1664
1782
  params.locale = this.localeValue;
1665
1783
  } else if (this.fetchAllLocales) {
1666
1784
  params.locale = "*";
1667
1785
  } else if (this.localesArray && this.localesArray.length > 0) {
1668
1786
  params.locales = this.localesArray.join(",");
1669
- } else if (this.defaultLocale != null && this.defaultLocale !== "") {
1670
- params.locale = this.defaultLocale;
1787
+ } else if (clientLocale != null && clientLocale !== "") {
1788
+ params.locale = clientLocale;
1671
1789
  }
1672
1790
  if (this.pageNumber !== void 0) {
1673
1791
  params.page = this.pageNumber;
@@ -1691,16 +1809,23 @@ var _ContentQueryBuilder = class _ContentQueryBuilder {
1691
1809
  chunkMWMSFIWN_cjs.__name(_ContentQueryBuilder, "ContentQueryBuilder");
1692
1810
  var ContentQueryBuilder = _ContentQueryBuilder;
1693
1811
  var _ContentByIdBuilder = class _ContentByIdBuilder {
1694
- constructor(httpClient, contentType, id, defaultLocale) {
1812
+ /**
1813
+ * @param locale - The client's locale (`withLocale()`, `updateConfig({ locale })`), sent when the
1814
+ * read names none: the function `TypedRelyIOClient` hands over, or a locale fixed for it
1815
+ */
1816
+ constructor(httpClient, contentType, id, locale) {
1695
1817
  this.httpClient = httpClient;
1696
1818
  this.contentType = contentType;
1697
1819
  this.id = id;
1698
- this.defaultLocale = defaultLocale;
1699
1820
  chunkMWMSFIWN_cjs.__publicField(this, "includeRelations");
1700
1821
  chunkMWMSFIWN_cjs.__publicField(this, "localeValue");
1701
1822
  chunkMWMSFIWN_cjs.__publicField(this, "localesArray");
1702
1823
  chunkMWMSFIWN_cjs.__publicField(this, "fetchAllLocales", false);
1703
1824
  chunkMWMSFIWN_cjs.__publicField(this, "childrenOptions");
1825
+ chunkMWMSFIWN_cjs.__publicField(this, "requestHeaders");
1826
+ /** The client's locale, asked when the read is sent rather than copied when it was built. */
1827
+ chunkMWMSFIWN_cjs.__publicField(this, "clientLocale");
1828
+ this.clientLocale = readClientLocale(locale);
1704
1829
  }
1705
1830
  /**
1706
1831
  * Include related content in the response
@@ -1731,6 +1856,15 @@ var _ContentByIdBuilder = class _ContentByIdBuilder {
1731
1856
  this.fetchAllLocales = false;
1732
1857
  return this;
1733
1858
  }
1859
+ /**
1860
+ * Headers this one read adds to the client's own, e.g. `X-RelyIO-Dataset` to read another dataset
1861
+ * the key can see. A name the client already sends is replaced for this request only; the client
1862
+ * is not changed.
1863
+ */
1864
+ headers(headers) {
1865
+ this.requestHeaders = headers;
1866
+ return this;
1867
+ }
1734
1868
  /**
1735
1869
  * Fetch content in all available locales
1736
1870
  * Returns default locale at top level, others in _metadata.localizations
@@ -1761,14 +1895,15 @@ var _ContentByIdBuilder = class _ContentByIdBuilder {
1761
1895
  if (this.includeRelations != null) {
1762
1896
  params.include = this.includeRelations.join(",");
1763
1897
  }
1898
+ const clientLocale = this.clientLocale();
1764
1899
  if (this.localeValue != null && this.localeValue !== "") {
1765
1900
  params.locale = this.localeValue;
1766
1901
  } else if (this.fetchAllLocales) {
1767
1902
  params.locale = "*";
1768
1903
  } else if (this.localesArray && this.localesArray.length > 0) {
1769
1904
  params.locales = this.localesArray.join(",");
1770
- } else if (this.defaultLocale != null && this.defaultLocale !== "") {
1771
- params.locale = this.defaultLocale;
1905
+ } else if (clientLocale != null && clientLocale !== "") {
1906
+ params.locale = clientLocale;
1772
1907
  }
1773
1908
  if (this.childrenOptions != null) {
1774
1909
  if (this.childrenOptions.contentTypes) {
@@ -1784,7 +1919,11 @@ var _ContentByIdBuilder = class _ContentByIdBuilder {
1784
1919
  return await this.httpClient.fetchPayload(
1785
1920
  "/v1/{contentType}/{id}",
1786
1921
  "get",
1787
- { path: { contentType: this.contentType, id: this.id }, query: params }
1922
+ {
1923
+ path: { contentType: this.contentType, id: this.id },
1924
+ query: params,
1925
+ headers: this.requestHeaders
1926
+ }
1788
1927
  );
1789
1928
  } catch (error) {
1790
1929
  if (isResourceNotFound(error)) return null;
@@ -1814,6 +1953,14 @@ function writeHeaders(options) {
1814
1953
  return Object.keys(headers).length > 0 ? headers : void 0;
1815
1954
  }
1816
1955
  chunkMWMSFIWN_cjs.__name(writeHeaders, "writeHeaders");
1956
+ function requestHeaders(headers, typed) {
1957
+ if (headers === void 0) return typed;
1958
+ if (typed === void 0) return headers;
1959
+ const typedNames = new Set(Object.keys(typed).map((name) => name.toLowerCase()));
1960
+ const own = Object.entries(headers).filter(([name]) => !typedNames.has(name.toLowerCase()));
1961
+ return { ...Object.fromEntries(own), ...typed };
1962
+ }
1963
+ chunkMWMSFIWN_cjs.__name(requestHeaders, "requestHeaders");
1817
1964
  function entityTagOf(response) {
1818
1965
  return headerValue(response.headers, HEADER_ENTITY_TAG.name);
1819
1966
  }
@@ -1821,22 +1968,28 @@ chunkMWMSFIWN_cjs.__name(entityTagOf, "entityTagOf");
1821
1968
 
1822
1969
  // src/content/content-client.ts
1823
1970
  var _ContentClient = class _ContentClient {
1824
- constructor(httpClient, contentType, defaultLocale) {
1971
+ /**
1972
+ * @param locale - The client's locale: the function `TypedRelyIOClient` hands over, asked on
1973
+ * every call, or a locale fixed for this client
1974
+ */
1975
+ constructor(httpClient, contentType, locale) {
1825
1976
  this.httpClient = httpClient;
1826
1977
  this.contentType = contentType;
1827
- this.defaultLocale = defaultLocale;
1978
+ /** The client's locale, asked on every call rather than copied when this client was taken. */
1979
+ chunkMWMSFIWN_cjs.__publicField(this, "clientLocale");
1980
+ this.clientLocale = readClientLocale(locale);
1828
1981
  }
1829
1982
  /**
1830
1983
  * Create a new query builder for this content type
1831
1984
  */
1832
1985
  query() {
1833
- return new ContentQueryBuilder(this.httpClient, this.contentType, this.defaultLocale);
1986
+ return new ContentQueryBuilder(this.httpClient, this.contentType, this.clientLocale);
1834
1987
  }
1835
1988
  /**
1836
1989
  * Find content by ID with fluent options
1837
1990
  */
1838
1991
  findById(id) {
1839
- return new ContentByIdBuilder(this.httpClient, this.contentType, id, this.defaultLocale);
1992
+ return new ContentByIdBuilder(this.httpClient, this.contentType, id, this.clientLocale);
1840
1993
  }
1841
1994
  /**
1842
1995
  * Fetch all content items
@@ -1883,7 +2036,8 @@ var _ContentClient = class _ContentClient {
1883
2036
  /**
1884
2037
  * Fetch a single content item by ID
1885
2038
  * @param id - Entry ID or slug
1886
- * @param options - Fetch options (include, locale)
2039
+ * @param options - Fetch options (include, locale, and `headers` this one request adds — e.g.
2040
+ * `{ 'X-RelyIO-Dataset': 'staging' }` to read a dataset other than the client's)
1887
2041
  */
1888
2042
  async fetchOne(id, options) {
1889
2043
  const builder = this.findById(id);
@@ -1893,6 +2047,9 @@ var _ContentClient = class _ContentClient {
1893
2047
  if (options?.locale != null && options.locale !== "") {
1894
2048
  builder.locale(options.locale);
1895
2049
  }
2050
+ if (options?.headers != null) {
2051
+ builder.headers(options.headers);
2052
+ }
1896
2053
  return builder.execute();
1897
2054
  }
1898
2055
  /**
@@ -1907,16 +2064,21 @@ var _ContentClient = class _ContentClient {
1907
2064
  * `entityTag` is `undefined` when the response came from the cache: a cached
1908
2065
  * body cannot know the current version, and an absent tag beats a stale one
1909
2066
  * that would refuse the next write.
2067
+ *
2068
+ * Unlike {@link fetchOne}, a missing entry is not `null`: the service's
2069
+ * `RESOURCE_NOT_FOUND` is thrown as it arrived, with the `kind` that says
2070
+ * whether the entry, the content type or the schema was missing. `headers`
2071
+ * are the ones this read adds, as for `fetchOne`.
1910
2072
  */
1911
2073
  async fetchOneWithTag(id, options) {
1912
2074
  const query = {};
1913
2075
  if (options?.include != null) query.include = options.include.join(",");
1914
- const locale = options?.locale ?? this.defaultLocale;
2076
+ const locale = options?.locale ?? this.clientLocale();
1915
2077
  if (locale != null && locale !== "") query.locale = locale;
1916
2078
  const response = await this.httpClient.send(
1917
2079
  "/v1/{contentType}/{id}",
1918
2080
  "get",
1919
- { path: { contentType: this.contentType, id }, query }
2081
+ { path: { contentType: this.contentType, id }, query, headers: options?.headers }
1920
2082
  );
1921
2083
  return { entry: readPayload(response.body), entityTag: entityTagOf(response) };
1922
2084
  }
@@ -1938,17 +2100,21 @@ var _ContentClient = class _ContentClient {
1938
2100
  /**
1939
2101
  * Create a new content item
1940
2102
  * @param data - Content data
1941
- * @param options - Create options (slug, locale, parentId, sortOrder)
2103
+ * @param options - Create options (slug, locale, parentId, sortOrder), and `headers` this one
2104
+ * request adds — e.g. `{ 'X-RelyIO-Dataset': 'staging' }` to write a dataset other than the
2105
+ * client's. They are sent, never put in the body; `idempotencyKey` still wins over an
2106
+ * `Idempotency-Key` among them. `locale` defaults to the client's own (`withLocale()`).
1942
2107
  */
1943
2108
  async create(data, options) {
1944
- const { idempotencyKey, ...body } = options ?? {};
2109
+ const { idempotencyKey, headers, locale, ...body } = options ?? {};
2110
+ const sent = callLocale(locale, this.clientLocale());
1945
2111
  return this.httpClient.fetchPayload(
1946
2112
  "/v1/{contentType}",
1947
2113
  "post",
1948
2114
  {
1949
2115
  path: { contentType: this.contentType },
1950
- body: { content: data, ...body },
1951
- headers: writeHeaders({ idempotencyKey })
2116
+ body: { content: data, ...body, ...sent === void 0 ? {} : { locale: sent } },
2117
+ headers: requestHeaders(headers, writeHeaders({ idempotencyKey }))
1952
2118
  }
1953
2119
  );
1954
2120
  }
@@ -1956,17 +2122,21 @@ var _ContentClient = class _ContentClient {
1956
2122
  * Update a content item by ID (partial update)
1957
2123
  * @param id - Entry ID
1958
2124
  * @param data - Fields to update
1959
- * @param options - Update options (slug, locale, parentId, sortOrder)
2125
+ * @param options - Update options (slug, locale, parentId, sortOrder), and `headers` this one
2126
+ * request adds, as for {@link create}. `ifMatch` and `idempotencyKey` win over the same
2127
+ * header among them. `locale` defaults to the client's own, the locale whose entity tag
2128
+ * {@link fetchOneWithTag} reads, so the tag it hands back is checked against the same locale.
1960
2129
  */
1961
2130
  async update(id, data, options) {
1962
- const { idempotencyKey, ifMatch, ...body } = options ?? {};
2131
+ const { idempotencyKey, ifMatch, headers, locale, ...body } = options ?? {};
2132
+ const sent = callLocale(locale, this.clientLocale());
1963
2133
  return this.httpClient.fetchPayload(
1964
2134
  "/v1/{contentType}/{id}",
1965
2135
  "patch",
1966
2136
  {
1967
2137
  path: { contentType: this.contentType, id },
1968
- body: { content: data, ...body },
1969
- headers: writeHeaders({ idempotencyKey, ifMatch })
2138
+ body: { content: data, ...body, ...sent === void 0 ? {} : { locale: sent } },
2139
+ headers: requestHeaders(headers, writeHeaders({ idempotencyKey, ifMatch }))
1970
2140
  }
1971
2141
  );
1972
2142
  }
@@ -1997,32 +2167,35 @@ var _ContentClient = class _ContentClient {
1997
2167
  * Get version history for one locale of an entry.
1998
2168
  *
1999
2169
  * Versions are numbered per locale, so this is one locale's timeline: `locale` defaults to the
2000
- * schema default, no fallback is applied, and a locale with no history returns an empty array
2001
- * rather than another locale's rows. Every row names the locale it belongs to.
2170
+ * client's own (`withLocale()`), else the schema default; no fallback is applied, and a locale
2171
+ * with no history returns an empty array rather than another locale's rows. Every row names the
2172
+ * locale it belongs to.
2002
2173
  */
2003
2174
  async versions(id, options) {
2175
+ const locale = callLocale(options?.locale, this.clientLocale());
2004
2176
  return this.httpClient.fetchPayload("/v1/{contentType}/{id}/versions", "get", {
2005
2177
  path: { contentType: this.contentType, id },
2006
2178
  query: {
2007
2179
  limit: options?.limit ?? 10,
2008
- ...options?.locale === void 0 ? {} : { locale: options.locale }
2180
+ ...locale === void 0 ? {} : { locale }
2009
2181
  }
2010
2182
  });
2011
2183
  }
2012
2184
  /**
2013
2185
  * Get a specific version of an entry.
2014
2186
  *
2015
- * `locale` defaults to the schema default. Version numbers are per locale, so version 2 is
2016
- * different content in each one.
2187
+ * `locale` defaults to the client's own (`withLocale()`), else the schema default. Version
2188
+ * numbers are per locale, so version 2 is different content in each one.
2017
2189
  */
2018
2190
  async version(id, versionNumber, options) {
2191
+ const locale = callLocale(options?.locale, this.clientLocale());
2019
2192
  return this.httpClient.fetchPayload("/v1/{contentType}/{id}/versions/{versionNumber}", "get", {
2020
2193
  path: {
2021
2194
  contentType: this.contentType,
2022
2195
  id,
2023
2196
  versionNumber: String(versionNumber)
2024
2197
  },
2025
- query: options?.locale === void 0 ? void 0 : { locale: options.locale }
2198
+ query: locale === void 0 ? void 0 : { locale }
2026
2199
  });
2027
2200
  }
2028
2201
  /**
@@ -2061,8 +2234,9 @@ var _ContentClient = class _ContentClient {
2061
2234
  if (options?.include != null) {
2062
2235
  params.include = options.include.join(",");
2063
2236
  }
2064
- if (options?.locale != null && options.locale !== "") {
2065
- params.locale = options.locale;
2237
+ const locale = options?.locale ?? this.clientLocale();
2238
+ if (locale != null && locale !== "") {
2239
+ params.locale = locale;
2066
2240
  }
2067
2241
  return this.httpClient.fetchPayload(
2068
2242
  "/v1/{contentType}/{id}/children",
@@ -2080,19 +2254,20 @@ var _ContentClient = class _ContentClient {
2080
2254
  * Create a new ContentClient for a different content type
2081
2255
  */
2082
2256
  ofType(contentType) {
2083
- return new _ContentClient(this.httpClient, contentType, this.defaultLocale);
2257
+ return new _ContentClient(this.httpClient, contentType, this.clientLocale);
2084
2258
  }
2085
2259
  /**
2086
- * Create a new ContentClient with a different default locale
2260
+ * Create a new ContentClient with a different default locale, fixed: an update of the client's
2261
+ * locale (`updateConfig({ locale })`) does not reach it
2087
2262
  */
2088
2263
  withLocale(locale) {
2089
2264
  return new _ContentClient(this.httpClient, this.contentType, locale);
2090
2265
  }
2091
2266
  /**
2092
- * Get the current default locale
2267
+ * Get the default locale as it is now: the client's, or the one `withLocale()` fixed
2093
2268
  */
2094
2269
  getDefaultLocale() {
2095
- return this.defaultLocale;
2270
+ return this.clientLocale();
2096
2271
  }
2097
2272
  };
2098
2273
  chunkMWMSFIWN_cjs.__name(_ContentClient, "ContentClient");
@@ -2101,7 +2276,7 @@ var ContentClient = _ContentClient;
2101
2276
  // src/errors/file-errors.ts
2102
2277
  var _FileUploadError = class _FileUploadError extends SDKError {
2103
2278
  constructor(code, message, options = {}) {
2104
- super(message, options.statusCode, options.cause, { fileErrorCode: code });
2279
+ super(message, options.statusCode, options.cause, { ...options.context, fileErrorCode: code });
2105
2280
  chunkMWMSFIWN_cjs.__publicField(this, "code");
2106
2281
  chunkMWMSFIWN_cjs.__publicField(this, "retryable");
2107
2282
  this.code = code;
@@ -2148,11 +2323,52 @@ function validateFile(file, constraints = {}) {
2148
2323
  }
2149
2324
  chunkMWMSFIWN_cjs.__name(validateFile, "validateFile");
2150
2325
 
2326
+ // src/errors/file-error-codes.ts
2327
+ var FILE_ERROR_CODES = [
2328
+ "UPLOAD_TOO_LARGE",
2329
+ "UNSUPPORTED_MIME",
2330
+ "DIMENSION_OUT_OF_RANGE",
2331
+ "ASPECT_RATIO",
2332
+ "CORRUPT_FILE",
2333
+ "UNSUPPORTED_CODEC",
2334
+ "IMAGES_LIMIT_EXCEEDED",
2335
+ "STREAM_ENCODE_FAILED",
2336
+ "SCAN_INFECTED",
2337
+ "INTERNAL"
2338
+ ];
2339
+
2151
2340
  // src/content/asset-client.ts
2341
+ function assertWaitOptions(options) {
2342
+ const { timeoutMs, pollIntervalMs } = options;
2343
+ if (timeoutMs !== void 0 && !(typeof timeoutMs === "number" && timeoutMs >= 0 && timeoutMs <= MAX_TIMER_DELAY_MS)) {
2344
+ throw new Error(
2345
+ `timeoutMs must be a number of milliseconds from 0 to ${MAX_TIMER_DELAY_MS}, and got ${String(timeoutMs)}`
2346
+ );
2347
+ }
2348
+ if (pollIntervalMs !== void 0 && !isTimerDelay(pollIntervalMs)) {
2349
+ throw new Error(
2350
+ `pollIntervalMs must be a number of milliseconds from 1 to ${MAX_TIMER_DELAY_MS} \u2014 a timer given 0, NaN, Infinity or more fires at once \u2014 and got ${String(pollIntervalMs)}`
2351
+ );
2352
+ }
2353
+ }
2354
+ chunkMWMSFIWN_cjs.__name(assertWaitOptions, "assertWaitOptions");
2152
2355
  function hasText(value) {
2153
2356
  return value != null && value !== "";
2154
2357
  }
2155
2358
  chunkMWMSFIWN_cjs.__name(hasText, "hasText");
2359
+ function processingFailure(subject, stored) {
2360
+ const storedCode = stored?.code;
2361
+ const code = FILE_ERROR_CODES.find((known) => known === storedCode) ?? "PROCESSING_FAILED";
2362
+ return new FileUploadError(
2363
+ code,
2364
+ `Asset ${subject}: ${stored?.message ?? "File processing failed"}`,
2365
+ {
2366
+ retriable: stored?.retriable === true,
2367
+ context: { processingError: stored }
2368
+ }
2369
+ );
2370
+ }
2371
+ chunkMWMSFIWN_cjs.__name(processingFailure, "processingFailure");
2156
2372
  async function computeSha256Hex(blob) {
2157
2373
  try {
2158
2374
  const subtle = globalThis.crypto?.subtle;
@@ -2168,11 +2384,17 @@ async function computeSha256Hex(blob) {
2168
2384
  }
2169
2385
  chunkMWMSFIWN_cjs.__name(computeSha256Hex, "computeSha256Hex");
2170
2386
  var _AssetFileBuilder = class _AssetFileBuilder {
2171
- constructor(httpClient, assetType, entryUid, defaultLocale) {
2387
+ /**
2388
+ * @param locale - The client's locale, sent when a call names none: the function
2389
+ * `TypedRelyIOClient` hands over, or a locale fixed for this builder
2390
+ */
2391
+ constructor(httpClient, assetType, entryUid, locale) {
2172
2392
  this.httpClient = httpClient;
2173
2393
  this.assetType = assetType;
2174
2394
  this.entryUid = entryUid;
2175
- this.defaultLocale = defaultLocale;
2395
+ /** The client's locale, asked on every call rather than copied when this builder was taken. */
2396
+ chunkMWMSFIWN_cjs.__publicField(this, "clientLocale");
2397
+ this.clientLocale = readClientLocale(locale);
2176
2398
  }
2177
2399
  /**
2178
2400
  * The path parameters every `/file/*` route on this builder takes.
@@ -2185,7 +2407,16 @@ var _AssetFileBuilder = class _AssetFileBuilder {
2185
2407
  return { contentType: this.assetType, id: this.entryUid };
2186
2408
  }
2187
2409
  withLocale(locale) {
2188
- return locale ?? this.defaultLocale;
2410
+ return locale ?? this.clientLocale();
2411
+ }
2412
+ /**
2413
+ * The `locale` body field of a multipart call: the session's, else the client's. The API finds
2414
+ * the binding by it, so a session a `withLocale()` client opened must name the same locale on
2415
+ * every later call or its `objectKey` is refused as another binding's.
2416
+ */
2417
+ localeField(locale) {
2418
+ const sent = this.withLocale(locale);
2419
+ return hasText(sent) ? { locale: sent } : {};
2189
2420
  }
2190
2421
  /**
2191
2422
  * Issue a single upload target — an R2 presigned PUT (image/file) or a
@@ -2244,7 +2475,7 @@ var _AssetFileBuilder = class _AssetFileBuilder {
2244
2475
  uploadId: target.uploadId,
2245
2476
  objectKey: target.objectKey,
2246
2477
  partNumber: target.partNumber,
2247
- ...hasText(target.locale) ? { locale: target.locale } : {}
2478
+ ...this.localeField(target.locale)
2248
2479
  }
2249
2480
  });
2250
2481
  }
@@ -2255,7 +2486,7 @@ var _AssetFileBuilder = class _AssetFileBuilder {
2255
2486
  body: {
2256
2487
  uploadId: target.uploadId,
2257
2488
  objectKey: target.objectKey,
2258
- ...hasText(target.locale) ? { locale: target.locale } : {}
2489
+ ...this.localeField(target.locale)
2259
2490
  }
2260
2491
  });
2261
2492
  return payload.parts;
@@ -2268,7 +2499,7 @@ var _AssetFileBuilder = class _AssetFileBuilder {
2268
2499
  uploadId: target.uploadId,
2269
2500
  objectKey: target.objectKey,
2270
2501
  parts: target.parts.map((p) => ({ partNumber: p.partNumber, etag: p.etag })),
2271
- ...hasText(target.locale) ? { locale: target.locale } : {}
2502
+ ...this.localeField(target.locale)
2272
2503
  }
2273
2504
  });
2274
2505
  }
@@ -2279,7 +2510,7 @@ var _AssetFileBuilder = class _AssetFileBuilder {
2279
2510
  body: {
2280
2511
  uploadId: target.uploadId,
2281
2512
  objectKey: target.objectKey,
2282
- ...hasText(target.locale) ? { locale: target.locale } : {}
2513
+ ...this.localeField(target.locale)
2283
2514
  }
2284
2515
  });
2285
2516
  }
@@ -2299,6 +2530,25 @@ var _AssetFileBuilder = class _AssetFileBuilder {
2299
2530
  { path: this.target, query: params }
2300
2531
  );
2301
2532
  }
2533
+ /**
2534
+ * Read where this file's upload got to — `status`, and the processing stage, progress and error
2535
+ * the pipeline recorded — whatever the asset entry is published in.
2536
+ *
2537
+ * An asset is a draft until its file is ready, and stays one when its file fails, so a key that
2538
+ * reads published content finds no entry for an upload in flight or one that failed. This read
2539
+ * answers for the binding itself. It takes the capability the upload calls take, so a read-only
2540
+ * key is refused with `ReadOnlyKeyError`, and it never returns the entry's fields.
2541
+ */
2542
+ async status(options = {}) {
2543
+ const params = {};
2544
+ const locale = this.withLocale(options.locale);
2545
+ if (hasText(locale)) params.locale = locale;
2546
+ return this.httpClient.fetchPayload(
2547
+ "/v1/{contentType}/{id}/file",
2548
+ "get",
2549
+ { path: this.target, query: params }
2550
+ );
2551
+ }
2302
2552
  /**
2303
2553
  * Update the binding's delivery visibility (token-based; no byte relocation —
2304
2554
  * a downgrade purges the gateway cache). Returns the refreshed asset entry.
@@ -2337,14 +2587,31 @@ var _AssetFileBuilder = class _AssetFileBuilder {
2337
2587
  });
2338
2588
  }
2339
2589
  /**
2340
- * Poll the asset entry until its file binding reaches `ready` (resolves with
2341
- * the ready entry) or `failed` (throws). Times out after `timeoutMs`.
2590
+ * Poll the asset entry until its file binding reaches `ready`, and resolve with the ready entry.
2591
+ *
2592
+ * Each poll reads the entry through the key's lens. When that finds nothing — a key that reads
2593
+ * published content, and an asset that is a draft until its file is ready and stays one when it
2594
+ * fails — the poll reads the binding itself ({@link status}), so a failed upload rejects as one
2595
+ * rather than running out the limit (REL-1481).
2596
+ *
2597
+ * @throws {FileUploadError} When processing failed. `code` is the processing code the service
2598
+ * stored — `UNSUPPORTED_CODEC`, `CORRUPT_FILE` and so on — or `PROCESSING_FAILED` where it
2599
+ * stored none this SDK knows; `context.processingError` holds what it stored, and `retryable`
2600
+ * is its `retriable` hint.
2601
+ * @throws {TimeoutError} When `timeoutMs` passes first. The file may still become ready; the
2602
+ * error's `context.timeout` is the limit and `context.fileStatus` the status last seen. A file
2603
+ * that is `ready` in an asset the key's lens never shows — created with `publish: false`, or
2604
+ * awaiting a workflow — ends here too, and the message says so.
2605
+ * @throws {ReadOnlyKeyError} When the key's lens does not show the asset and the key may not read
2606
+ * its binding either: nothing it can read would ever report the file.
2342
2607
  */
2343
2608
  async waitUntilReady(options = {}) {
2609
+ assertWaitOptions(options);
2344
2610
  const timeoutMs = options.timeoutMs ?? 6e4;
2345
2611
  const pollIntervalMs = options.pollIntervalMs ?? 1500;
2346
2612
  const locale = this.withLocale(options.locale);
2347
2613
  const deadline = Date.now() + timeoutMs;
2614
+ const subject = `${this.assetType}/${this.entryUid}`;
2348
2615
  for (; ; ) {
2349
2616
  const builder = new ContentByIdBuilder(
2350
2617
  this.httpClient,
@@ -2354,15 +2621,24 @@ var _AssetFileBuilder = class _AssetFileBuilder {
2354
2621
  );
2355
2622
  if (hasText(locale)) builder.locale(locale);
2356
2623
  const entry = await builder.execute();
2357
- const status = entry?.file?.status;
2358
- if (entry && status === "ready") return entry;
2359
- if (status === "failed") {
2360
- const message = entry?.file?.processing?.error?.message ?? "File processing failed";
2361
- throw new Error(`Asset ${this.assetType}/${this.entryUid}: ${message}`);
2624
+ let status;
2625
+ if (entry === null) {
2626
+ const binding = await this.status({ ...hasText(locale) ? { locale } : {} });
2627
+ status = binding.status;
2628
+ if (status === "failed") throw processingFailure(subject, binding.processing?.error);
2629
+ } else {
2630
+ status = entry.file?.status;
2631
+ if (status === "ready") return entry;
2632
+ if (status === "failed") throw processingFailure(subject, entry.file?.processing?.error);
2362
2633
  }
2363
2634
  if (Date.now() >= deadline) {
2364
- throw new Error(
2365
- `Timed out after ${timeoutMs}ms waiting for ${this.assetType}/${this.entryUid} (status: ${status ?? "unknown"})`
2635
+ const unseen = entry === null && status === "ready" ? "; the file is ready and this key's lens does not show the asset \u2014 it is not published" : "";
2636
+ throw new TimeoutError(
2637
+ `Timed out after ${timeoutMs}ms waiting for ${subject} (status: ${status ?? "unknown"})${unseen}`,
2638
+ timeoutMs,
2639
+ void 0,
2640
+ void 0,
2641
+ { fileStatus: status }
2366
2642
  );
2367
2643
  }
2368
2644
  await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
@@ -2372,11 +2648,14 @@ var _AssetFileBuilder = class _AssetFileBuilder {
2372
2648
  chunkMWMSFIWN_cjs.__name(_AssetFileBuilder, "AssetFileBuilder");
2373
2649
  var AssetFileBuilder = _AssetFileBuilder;
2374
2650
  var _AssetUploadBuilder = class _AssetUploadBuilder {
2375
- constructor(httpClient, assetType, file, defaultLocale) {
2651
+ /**
2652
+ * @param locale - The client's locale, the upload's when `.locale()` names none: the function
2653
+ * `TypedRelyIOClient` hands over, or a locale fixed for this upload
2654
+ */
2655
+ constructor(httpClient, assetType, file, locale) {
2376
2656
  this.httpClient = httpClient;
2377
2657
  this.assetType = assetType;
2378
2658
  this.file = file;
2379
- this.defaultLocale = defaultLocale;
2380
2659
  chunkMWMSFIWN_cjs.__publicField(this, "_fileName");
2381
2660
  chunkMWMSFIWN_cjs.__publicField(this, "_visibility");
2382
2661
  chunkMWMSFIWN_cjs.__publicField(this, "_locale");
@@ -2388,6 +2667,9 @@ var _AssetUploadBuilder = class _AssetUploadBuilder {
2388
2667
  chunkMWMSFIWN_cjs.__publicField(this, "_onProgress");
2389
2668
  chunkMWMSFIWN_cjs.__publicField(this, "_validate");
2390
2669
  chunkMWMSFIWN_cjs.__publicField(this, "_maxPartRetries", 3);
2670
+ /** The client's locale, read once when the upload starts. */
2671
+ chunkMWMSFIWN_cjs.__publicField(this, "clientLocale");
2672
+ this.clientLocale = readClientLocale(locale);
2391
2673
  }
2392
2674
  /** Subscribe to client → storage upload progress (single PUT + multipart). */
2393
2675
  onProgress(callback) {
@@ -2437,8 +2719,14 @@ var _AssetUploadBuilder = class _AssetUploadBuilder {
2437
2719
  this._slug = slug;
2438
2720
  return this;
2439
2721
  }
2440
- /** Wait until the file binding becomes `ready` before resolving. */
2722
+ /**
2723
+ * Wait until the file binding becomes `ready` before resolving.
2724
+ *
2725
+ * @throws {Error} When `timeoutMs` or `pollIntervalMs` is one the wait cannot honour — refused
2726
+ * here, before anything is uploaded, rather than after the bytes are in.
2727
+ */
2441
2728
  waitForReady(options = true) {
2729
+ if (typeof options === "object") assertWaitOptions(options);
2442
2730
  if (options === false) {
2443
2731
  this._waitForReady = false;
2444
2732
  } else {
@@ -2453,7 +2741,8 @@ var _AssetUploadBuilder = class _AssetUploadBuilder {
2453
2741
  if (!fileName) {
2454
2742
  throw new Error('A file name is required: pass a File, or call .filename("name.ext")');
2455
2743
  }
2456
- const locale = this._locale ?? this.defaultLocale;
2744
+ const clientLocale = this.clientLocale();
2745
+ const locale = this._locale ?? clientLocale;
2457
2746
  if (this._validate) {
2458
2747
  const violations = validateFile(this.file, this._validate);
2459
2748
  if (violations.length > 0) {
@@ -2464,7 +2753,7 @@ var _AssetUploadBuilder = class _AssetUploadBuilder {
2464
2753
  }
2465
2754
  }
2466
2755
  const hash = await computeSha256Hex(this.file);
2467
- const client = new AssetClient(this.httpClient, this.assetType, this.defaultLocale);
2756
+ const client = new AssetClient(this.httpClient, this.assetType, clientLocale);
2468
2757
  const created = await client.create(this._content, {
2469
2758
  ...hasText(this._slug) ? { slug: this._slug } : {},
2470
2759
  ...hasText(locale) ? { locale } : {},
@@ -2615,17 +2904,26 @@ var _AssetUploadBuilder = class _AssetUploadBuilder {
2615
2904
  chunkMWMSFIWN_cjs.__name(_AssetUploadBuilder, "AssetUploadBuilder");
2616
2905
  var AssetUploadBuilder = _AssetUploadBuilder;
2617
2906
  var _AssetClient = class _AssetClient {
2618
- constructor(httpClient, assetType, defaultLocale) {
2907
+ /**
2908
+ * @param locale - The client's locale: the function `TypedRelyIOClient` hands over, asked on
2909
+ * every call, or a locale fixed for this client
2910
+ */
2911
+ constructor(httpClient, assetType, locale) {
2619
2912
  this.httpClient = httpClient;
2620
2913
  this.assetType = assetType;
2621
- this.defaultLocale = defaultLocale;
2914
+ /** The client's locale, asked on every call rather than copied when this client was taken. */
2915
+ chunkMWMSFIWN_cjs.__publicField(this, "clientLocale");
2916
+ this.clientLocale = readClientLocale(locale);
2622
2917
  }
2623
- /** Create a query builder for this asset type. */
2918
+ /**
2919
+ * Create a query builder for this asset type. It resolves asset reads; its conditions name the
2920
+ * asset type's fields, which the `file` and `urls` blocks are not.
2921
+ */
2624
2922
  query() {
2625
2923
  return new ContentQueryBuilder(
2626
2924
  this.httpClient,
2627
2925
  this.assetType,
2628
- this.defaultLocale
2926
+ this.clientLocale
2629
2927
  );
2630
2928
  }
2631
2929
  /** Find an asset entry by id (or slug) with fluent options. */
@@ -2634,7 +2932,7 @@ var _AssetClient = class _AssetClient {
2634
2932
  this.httpClient,
2635
2933
  this.assetType,
2636
2934
  id,
2637
- this.defaultLocale
2935
+ this.clientLocale
2638
2936
  );
2639
2937
  }
2640
2938
  /** Fetch a single asset entry by id (or slug), or `null` if not found. */
@@ -2655,28 +2953,34 @@ var _AssetClient = class _AssetClient {
2655
2953
  * Create a new asset entry as a draft. The binary is uploaded separately via
2656
2954
  * `file(uid)` (or use the high-level `upload()` helper), and the entry
2657
2955
  * auto-publishes when its file becomes ready (unless `publish: false`).
2956
+ * `locale` defaults to the client's own (`withLocale()`).
2658
2957
  */
2659
2958
  async create(data, options) {
2660
- const { idempotencyKey, ...rest } = options ?? {};
2959
+ const { idempotencyKey, locale, ...rest } = options ?? {};
2960
+ const sent = callLocale(locale, this.clientLocale());
2661
2961
  return this.httpClient.fetchPayload(
2662
2962
  "/v1/{contentType}",
2663
2963
  "post",
2664
2964
  {
2665
2965
  path: { contentType: this.assetType },
2666
- body: { content: data, ...rest },
2966
+ body: { content: data, ...rest, ...sent === void 0 ? {} : { locale: sent } },
2667
2967
  headers: writeHeaders({ idempotencyKey })
2668
2968
  }
2669
2969
  );
2670
2970
  }
2671
- /** Update an asset entry's metadata fields (and/or slug). */
2971
+ /**
2972
+ * Update an asset entry's metadata fields (and/or slug). `locale` defaults to the client's own
2973
+ * (`withLocale()`).
2974
+ */
2672
2975
  async update(id, data, options) {
2673
- const { idempotencyKey, ifMatch, ...rest } = options ?? {};
2976
+ const { idempotencyKey, ifMatch, locale, ...rest } = options ?? {};
2977
+ const sent = callLocale(locale, this.clientLocale());
2674
2978
  return this.httpClient.fetchPayload(
2675
2979
  "/v1/{contentType}/{id}",
2676
2980
  "patch",
2677
2981
  {
2678
2982
  path: { contentType: this.assetType, id },
2679
- body: { content: data, ...rest },
2983
+ body: { content: data, ...rest, ...sent === void 0 ? {} : { locale: sent } },
2680
2984
  headers: writeHeaders({ idempotencyKey, ifMatch })
2681
2985
  }
2682
2986
  );
@@ -2690,21 +2994,24 @@ var _AssetClient = class _AssetClient {
2690
2994
  }
2691
2995
  /** Get the file sub-resource builder for a specific asset entry. */
2692
2996
  file(entryUid) {
2693
- return new AssetFileBuilder(this.httpClient, this.assetType, entryUid, this.defaultLocale);
2997
+ return new AssetFileBuilder(this.httpClient, this.assetType, entryUid, this.clientLocale);
2694
2998
  }
2695
2999
  /** Start a high-level upload (create → presign → PUT → confirm). */
2696
3000
  upload(file) {
2697
- return new AssetUploadBuilder(this.httpClient, this.assetType, file, this.defaultLocale);
3001
+ return new AssetUploadBuilder(this.httpClient, this.assetType, file, this.clientLocale);
2698
3002
  }
2699
3003
  /** Get the asset type id. */
2700
3004
  getAssetType() {
2701
3005
  return this.assetType;
2702
3006
  }
2703
- /** Get the current default locale. */
3007
+ /** Get the default locale as it is now: the client's, or the one `withLocale()` fixed. */
2704
3008
  getDefaultLocale() {
2705
- return this.defaultLocale;
3009
+ return this.clientLocale();
2706
3010
  }
2707
- /** Create a new AssetClient for this type with a different default locale. */
3011
+ /**
3012
+ * Create a new AssetClient for this type with a different default locale, fixed: an update of the
3013
+ * client's locale (`updateConfig({ locale })`) does not reach it.
3014
+ */
2708
3015
  withLocale(locale) {
2709
3016
  return new _AssetClient(this.httpClient, this.assetType, locale);
2710
3017
  }
@@ -2720,11 +3027,18 @@ chunkMWMSFIWN_cjs.__name(isAssetEntry, "isAssetEntry");
2720
3027
 
2721
3028
  // src/content/singleton-client.ts
2722
3029
  var _SingletonClient = class _SingletonClient {
2723
- constructor(httpClient, singletonKey) {
3030
+ /**
3031
+ * @param locale - The client's locale: the function `TypedRelyIOClient` hands over, asked on
3032
+ * every call, or a locale fixed for this client
3033
+ */
3034
+ constructor(httpClient, singletonKey, locale) {
2724
3035
  this.httpClient = httpClient;
2725
3036
  this.singletonKey = singletonKey;
2726
3037
  chunkMWMSFIWN_cjs.__publicField(this, "includeFields");
2727
3038
  chunkMWMSFIWN_cjs.__publicField(this, "localeValue");
3039
+ /** The client's locale, asked on every call rather than copied when this client was taken. */
3040
+ chunkMWMSFIWN_cjs.__publicField(this, "clientLocale");
3041
+ this.clientLocale = readClientLocale(locale);
2728
3042
  }
2729
3043
  /**
2730
3044
  * Include reference fields in the response
@@ -2747,11 +3061,6 @@ var _SingletonClient = class _SingletonClient {
2747
3061
  * @param options - Optional fetch options (alternative to fluent methods)
2748
3062
  */
2749
3063
  async fetch(options) {
2750
- const includeValue = options?.include ?? this.includeFields;
2751
- options?.locale ?? this.localeValue;
2752
- if (includeValue) {
2753
- includeValue.join(",");
2754
- }
2755
3064
  try {
2756
3065
  return (await this.fetchWithTag(options)).content;
2757
3066
  } catch (error) {
@@ -2769,7 +3078,7 @@ var _SingletonClient = class _SingletonClient {
2769
3078
  async fetchWithTag(options) {
2770
3079
  const query = {};
2771
3080
  const includeValue = options?.include ?? this.includeFields;
2772
- const localeValueFinal = options?.locale ?? this.localeValue;
3081
+ const localeValueFinal = options?.locale ?? this.localeValue ?? this.clientLocale();
2773
3082
  if (includeValue) query.include = includeValue.join(",");
2774
3083
  if (localeValueFinal != null && localeValueFinal !== "") query.locale = localeValueFinal;
2775
3084
  const response = await this.httpClient.send(
@@ -2783,11 +3092,12 @@ var _SingletonClient = class _SingletonClient {
2783
3092
  * Update singleton content. The API auto-publishes on PATCH — there is no
2784
3093
  * separate publish route; calling `update()` writes and publishes.
2785
3094
  * @param data - The content fields to update
2786
- * @param locale - Optional locale for localized content (overrides fluent locale)
3095
+ * @param locale - Optional locale for localized content (overrides the fluent locale, which
3096
+ * overrides the client's own)
2787
3097
  * @param options - `If-Match` / `Idempotency-Key`, both declared on this route
2788
3098
  */
2789
3099
  async update(data, locale, options) {
2790
- const finalLocale = locale ?? this.localeValue;
3100
+ const finalLocale = this.localeOf(locale);
2791
3101
  return this.httpClient.fetchPayload(
2792
3102
  "/v1/singletons/{key}",
2793
3103
  "patch",
@@ -2816,15 +3126,17 @@ var _SingletonClient = class _SingletonClient {
2816
3126
  * Get version history for one locale of the singleton.
2817
3127
  *
2818
3128
  * Versions are numbered per locale, so this is one locale's timeline: `locale` defaults to the
2819
- * schema default, no fallback is applied, and a locale with no history returns an empty array
2820
- * rather than another locale's rows. Every row names the locale it belongs to.
3129
+ * fluent `.locale()`, then the client's own (`withLocale()`), then the schema default; no
3130
+ * fallback is applied, and a locale with no history returns an empty array rather than another
3131
+ * locale's rows. Every row names the locale it belongs to.
2821
3132
  */
2822
3133
  async versions(options) {
3134
+ const locale = this.localeOf(options?.locale);
2823
3135
  const payload = await this.httpClient.fetchPayload("/v1/singletons/{key}/versions", "get", {
2824
3136
  path: { key: this.singletonKey },
2825
3137
  query: {
2826
3138
  limit: options?.limit ?? 10,
2827
- ...options?.locale === void 0 ? {} : { locale: options.locale }
3139
+ ...locale === void 0 ? {} : { locale }
2828
3140
  }
2829
3141
  });
2830
3142
  return payload.versions;
@@ -2832,20 +3144,25 @@ var _SingletonClient = class _SingletonClient {
2832
3144
  /**
2833
3145
  * Get a specific version of the singleton.
2834
3146
  *
2835
- * `locale` defaults to the schema default. Version numbers are per locale, so version 2 is
2836
- * different content in each one.
3147
+ * `locale` defaults to the fluent `.locale()`, then the client's own (`withLocale()`), then the
3148
+ * schema default. Version numbers are per locale, so version 2 is different content in each one.
2837
3149
  */
2838
3150
  async version(versionNumber, options) {
3151
+ const locale = this.localeOf(options?.locale);
2839
3152
  try {
2840
3153
  return await this.httpClient.fetchPayload("/v1/singletons/{key}/versions/{versionNumber}", "get", {
2841
3154
  path: { key: this.singletonKey, versionNumber: String(versionNumber) },
2842
- query: options?.locale === void 0 ? void 0 : { locale: options.locale }
3155
+ query: locale === void 0 ? void 0 : { locale }
2843
3156
  });
2844
3157
  } catch (error) {
2845
3158
  if (isResourceNotFound(error)) return null;
2846
3159
  throw error;
2847
3160
  }
2848
3161
  }
3162
+ /** The locale a write or history read sends: the call's, the fluent one, else the client's. */
3163
+ localeOf(named) {
3164
+ return callLocale(named ?? this.localeValue, this.clientLocale());
3165
+ }
2849
3166
  /**
2850
3167
  * Get the singleton key
2851
3168
  */
@@ -2866,11 +3183,18 @@ var SingletonClient = _SingletonClient;
2866
3183
 
2867
3184
  // src/content/settings-client.ts
2868
3185
  var _SettingsClient = class _SettingsClient {
2869
- constructor(httpClient, groupId) {
3186
+ /**
3187
+ * @param locale - The client's locale: the function `TypedRelyIOClient` hands over, asked on
3188
+ * every call, or a locale fixed for this client
3189
+ */
3190
+ constructor(httpClient, groupId, locale) {
2870
3191
  this.httpClient = httpClient;
2871
3192
  this.groupId = groupId;
2872
3193
  chunkMWMSFIWN_cjs.__publicField(this, "includeFields");
2873
3194
  chunkMWMSFIWN_cjs.__publicField(this, "localeValue");
3195
+ /** The client's locale, asked on every call rather than copied when this client was taken. */
3196
+ chunkMWMSFIWN_cjs.__publicField(this, "clientLocale");
3197
+ this.clientLocale = readClientLocale(locale);
2874
3198
  }
2875
3199
  /**
2876
3200
  * Include reference fields in the response
@@ -2893,11 +3217,6 @@ var _SettingsClient = class _SettingsClient {
2893
3217
  * @param options - Optional fetch options (alternative to fluent methods)
2894
3218
  */
2895
3219
  async fetch(options) {
2896
- const includeValue = options?.include ?? this.includeFields;
2897
- options?.locale ?? this.localeValue;
2898
- if (includeValue) {
2899
- includeValue.join(",");
2900
- }
2901
3220
  try {
2902
3221
  return (await this.fetchWithTag(options)).settings;
2903
3222
  } catch (error) {
@@ -2907,12 +3226,20 @@ var _SettingsClient = class _SettingsClient {
2907
3226
  }
2908
3227
  /**
2909
3228
  * Update settings group
3229
+ *
3230
+ * The fields travel as `content` beside an optional `locale`, the body the route validates
3231
+ * (`UpdateSettingsDto`); any other top-level key is refused `REQUEST_INVALID`.
3232
+ *
2910
3233
  * @param settings - The settings fields to update
2911
- * @param locale - Optional locale for localized settings (overrides fluent locale)
3234
+ * @param locale - Optional locale for localized settings (overrides the fluent locale, which
3235
+ * overrides the client's own)
2912
3236
  */
2913
3237
  async update(settings, locale, options) {
2914
- const finalLocale = locale ?? this.localeValue;
2915
- const body = finalLocale != null && finalLocale !== "" ? { ...settings, locale: finalLocale } : settings;
3238
+ const finalLocale = this.localeOf(locale);
3239
+ const body = {
3240
+ content: settings,
3241
+ ...finalLocale != null && finalLocale !== "" ? { locale: finalLocale } : {}
3242
+ };
2916
3243
  return this.httpClient.fetchPayload(
2917
3244
  "/v1/settings/{groupId}",
2918
3245
  "patch",
@@ -2927,7 +3254,7 @@ var _SettingsClient = class _SettingsClient {
2927
3254
  async fetchWithTag(options) {
2928
3255
  const query = {};
2929
3256
  const includeValue = options?.include ?? this.includeFields;
2930
- const localeValueFinal = options?.locale ?? this.localeValue;
3257
+ const localeValueFinal = options?.locale ?? this.localeValue ?? this.clientLocale();
2931
3258
  if (includeValue) query.include = includeValue.join(",");
2932
3259
  if (localeValueFinal != null && localeValueFinal !== "") query.locale = localeValueFinal;
2933
3260
  const response = await this.httpClient.send(
@@ -2939,16 +3266,20 @@ var _SettingsClient = class _SettingsClient {
2939
3266
  }
2940
3267
  /**
2941
3268
  * Reset settings to default values
2942
- * @param locale - Optional locale for localized settings
3269
+ *
3270
+ * The route reads its locale from `?locale=` and takes no body, so that is where it is sent.
3271
+ *
3272
+ * @param locale - Optional locale for localized settings (overrides the fluent locale, which
3273
+ * overrides the client's own)
2943
3274
  */
2944
3275
  async reset(locale, options) {
2945
- const finalLocale = locale ?? this.localeValue;
3276
+ const finalLocale = this.localeOf(locale);
2946
3277
  return this.httpClient.fetchPayload(
2947
3278
  "/v1/settings/{groupId}/reset",
2948
3279
  "post",
2949
3280
  {
2950
3281
  path: { groupId: this.groupId },
2951
- body: finalLocale != null && finalLocale !== "" ? { locale: finalLocale } : void 0,
3282
+ query: finalLocale != null && finalLocale !== "" ? { locale: finalLocale } : void 0,
2952
3283
  headers: writeHeaders(options)
2953
3284
  }
2954
3285
  );
@@ -2979,15 +3310,17 @@ var _SettingsClient = class _SettingsClient {
2979
3310
  * Get version history for one locale of the settings group.
2980
3311
  *
2981
3312
  * Versions are numbered per locale, so this is one locale's timeline: `locale` defaults to the
2982
- * schema default, no fallback is applied, and a locale with no history returns an empty array
2983
- * rather than another locale's rows. Every row names the locale it belongs to.
3313
+ * fluent `.locale()`, then the client's own (`withLocale()`), then the schema default; no
3314
+ * fallback is applied, and a locale with no history returns an empty array rather than another
3315
+ * locale's rows. Every row names the locale it belongs to.
2984
3316
  */
2985
3317
  async versions(options) {
3318
+ const locale = this.localeOf(options?.locale);
2986
3319
  const payload = await this.httpClient.fetchPayload("/v1/settings/{groupId}/versions", "get", {
2987
3320
  path: { groupId: this.groupId },
2988
3321
  query: {
2989
3322
  limit: options?.limit ?? 10,
2990
- ...options?.locale === void 0 ? {} : { locale: options.locale }
3323
+ ...locale === void 0 ? {} : { locale }
2991
3324
  }
2992
3325
  });
2993
3326
  return payload.versions;
@@ -2995,20 +3328,25 @@ var _SettingsClient = class _SettingsClient {
2995
3328
  /**
2996
3329
  * Get a specific version of the settings.
2997
3330
  *
2998
- * `locale` defaults to the schema default. Version numbers are per locale, so version 2 is
2999
- * different content in each one.
3331
+ * `locale` defaults to the fluent `.locale()`, then the client's own (`withLocale()`), then the
3332
+ * schema default. Version numbers are per locale, so version 2 is different content in each one.
3000
3333
  */
3001
3334
  async version(versionNumber, options) {
3335
+ const locale = this.localeOf(options?.locale);
3002
3336
  try {
3003
3337
  return await this.httpClient.fetchPayload("/v1/settings/{groupId}/versions/{versionNumber}", "get", {
3004
3338
  path: { groupId: this.groupId, versionNumber: String(versionNumber) },
3005
- query: options?.locale === void 0 ? void 0 : { locale: options.locale }
3339
+ query: locale === void 0 ? void 0 : { locale }
3006
3340
  });
3007
3341
  } catch (error) {
3008
3342
  if (isResourceNotFound(error)) return null;
3009
3343
  throw error;
3010
3344
  }
3011
3345
  }
3346
+ /** The locale a write or history read sends: the call's, the fluent one, else the client's. */
3347
+ localeOf(named) {
3348
+ return callLocale(named ?? this.localeValue, this.clientLocale());
3349
+ }
3012
3350
  /**
3013
3351
  * Get the settings group ID
3014
3352
  */
@@ -3029,14 +3367,31 @@ var SettingsClient = _SettingsClient;
3029
3367
 
3030
3368
  // src/search/search-client.ts
3031
3369
  var _SearchBuilder = class _SearchBuilder {
3032
- constructor(httpClient, query) {
3370
+ /**
3371
+ * @param locale - The client's locale (`withLocale()`, `updateConfig({ locale })`), sent when
3372
+ * `.locale()` names none: the function `TypedRelyIOClient` hands over, or a locale fixed for it
3373
+ */
3374
+ constructor(httpClient, query, locale) {
3033
3375
  this.httpClient = httpClient;
3034
3376
  chunkMWMSFIWN_cjs.__publicField(this, "query");
3035
3377
  chunkMWMSFIWN_cjs.__publicField(this, "searchOptions", {});
3378
+ chunkMWMSFIWN_cjs.__publicField(this, "requestHeaders");
3379
+ /** The client's locale, asked when the query is sent rather than copied when it was built. */
3380
+ chunkMWMSFIWN_cjs.__publicField(this, "clientLocale");
3036
3381
  this.query = query;
3382
+ this.clientLocale = readClientLocale(locale);
3037
3383
  }
3038
3384
  /**
3039
- * Search within a single content type
3385
+ * Headers this search adds to the client's own, e.g. `X-RelyIO-Dataset` to search another
3386
+ * dataset the key can see. A name the client already sends is replaced for this search only.
3387
+ */
3388
+ headers(headers) {
3389
+ this.requestHeaders = headers;
3390
+ return this;
3391
+ }
3392
+ /**
3393
+ * Search within a single content type. Where a schema types the client, the name is one of its
3394
+ * content types and the results are that type's entries.
3040
3395
  * @param contentType - Content type to search in
3041
3396
  */
3042
3397
  in(contentType) {
@@ -3045,7 +3400,8 @@ var _SearchBuilder = class _SearchBuilder {
3045
3400
  return this;
3046
3401
  }
3047
3402
  /**
3048
- * Search across multiple content types
3403
+ * Search across multiple content types. Where a schema types the client, the names are its content
3404
+ * types and the results are a union of their entries — every type's, for `'*'`.
3049
3405
  * @param contentTypes - Array of content types or '*' for all
3050
3406
  */
3051
3407
  across(contentTypes) {
@@ -3054,10 +3410,12 @@ var _SearchBuilder = class _SearchBuilder {
3054
3410
  return this;
3055
3411
  }
3056
3412
  /**
3057
- * Add filter conditions
3413
+ * Add filter conditions, on the fields the content types declare. A condition on `_metadata` is
3414
+ * refused here, before anything is sent.
3058
3415
  * @param conditions - Filter conditions
3059
3416
  */
3060
3417
  where(conditions) {
3418
+ refuseMetadataConditions(conditions);
3061
3419
  this.searchOptions.where = {
3062
3420
  ...this.searchOptions.where,
3063
3421
  ...conditions
@@ -3080,6 +3438,16 @@ var _SearchBuilder = class _SearchBuilder {
3080
3438
  this.searchOptions.fields = projection;
3081
3439
  return this;
3082
3440
  }
3441
+ /**
3442
+ * Order the results by a field instead of by relevance. Called again, it adds a tie-breaker.
3443
+ * `_score` names relevance itself.
3444
+ * @param field - Field path to sort by (e.g. `updatedAt`, `pricing.basePrice`)
3445
+ * @param direction - `asc` or `desc` (the route's default is `desc`)
3446
+ */
3447
+ sort(field, direction = "desc") {
3448
+ this.searchOptions.sort = [...this.searchOptions.sort ?? [], { field, direction }];
3449
+ return this;
3450
+ }
3083
3451
  /**
3084
3452
  * Limit number of results
3085
3453
  * @param count - Maximum results
@@ -3140,6 +3508,9 @@ var _SearchBuilder = class _SearchBuilder {
3140
3508
  if (this.searchOptions.fields) {
3141
3509
  body.fields = this.searchOptions.fields;
3142
3510
  }
3511
+ if (this.searchOptions.sort) {
3512
+ body.sort = this.searchOptions.sort;
3513
+ }
3143
3514
  if (this.searchOptions.limit !== void 0) {
3144
3515
  body.limit = this.searchOptions.limit;
3145
3516
  }
@@ -3152,13 +3523,14 @@ var _SearchBuilder = class _SearchBuilder {
3152
3523
  if (this.searchOptions.explain === true) {
3153
3524
  body.explain = true;
3154
3525
  }
3155
- if (this.searchOptions.locale != null && this.searchOptions.locale !== "") {
3156
- body.locale = this.searchOptions.locale;
3526
+ const locale = this.searchOptions.locale ?? this.clientLocale();
3527
+ if (locale != null && locale !== "") {
3528
+ body.locale = locale;
3157
3529
  }
3158
3530
  const response = await this.httpClient.send(
3159
3531
  "/v1/search",
3160
3532
  "post",
3161
- { body }
3533
+ { body, headers: this.requestHeaders }
3162
3534
  );
3163
3535
  return response.body;
3164
3536
  }
@@ -3178,11 +3550,18 @@ var _SearchBuilder = class _SearchBuilder {
3178
3550
  chunkMWMSFIWN_cjs.__name(_SearchBuilder, "SearchBuilder");
3179
3551
  var SearchBuilder = _SearchBuilder;
3180
3552
  var _AutocompleteBuilder = class _AutocompleteBuilder {
3181
- constructor(httpClient, query) {
3553
+ /**
3554
+ * @param locale - The client's locale (`withLocale()`, `updateConfig({ locale })`), sent when
3555
+ * `.locale()` names none: the function `TypedRelyIOClient` hands over, or a locale fixed for it
3556
+ */
3557
+ constructor(httpClient, query, locale) {
3182
3558
  this.httpClient = httpClient;
3183
3559
  chunkMWMSFIWN_cjs.__publicField(this, "query");
3184
3560
  chunkMWMSFIWN_cjs.__publicField(this, "autocompleteOptions", {});
3561
+ /** The client's locale, asked when the query is sent rather than copied when it was built. */
3562
+ chunkMWMSFIWN_cjs.__publicField(this, "clientLocale");
3185
3563
  this.query = query;
3564
+ this.clientLocale = readClientLocale(locale);
3186
3565
  }
3187
3566
  /**
3188
3567
  * Autocomplete within a content type
@@ -3208,10 +3587,12 @@ var _AutocompleteBuilder = class _AutocompleteBuilder {
3208
3587
  return this;
3209
3588
  }
3210
3589
  /**
3211
- * Add filter conditions
3590
+ * Add filter conditions, on the fields the content types declare. A condition on `_metadata` is
3591
+ * refused here, before anything is sent.
3212
3592
  * @param conditions - Filter conditions
3213
3593
  */
3214
3594
  where(conditions) {
3595
+ refuseMetadataConditions(conditions);
3215
3596
  this.autocompleteOptions.where = {
3216
3597
  ...this.autocompleteOptions.where,
3217
3598
  ...conditions
@@ -3258,8 +3639,9 @@ var _AutocompleteBuilder = class _AutocompleteBuilder {
3258
3639
  if (this.autocompleteOptions.limit !== void 0) {
3259
3640
  body.limit = this.autocompleteOptions.limit;
3260
3641
  }
3261
- if (this.autocompleteOptions.locale != null && this.autocompleteOptions.locale !== "") {
3262
- body.locale = this.autocompleteOptions.locale;
3642
+ const locale = this.autocompleteOptions.locale ?? this.clientLocale();
3643
+ if (locale != null && locale !== "") {
3644
+ body.locale = locale;
3263
3645
  }
3264
3646
  const response = await this.httpClient.send("/v1/search/autocomplete", "post", { body });
3265
3647
  return response.body;
@@ -3274,11 +3656,27 @@ var _AutocompleteBuilder = class _AutocompleteBuilder {
3274
3656
  chunkMWMSFIWN_cjs.__name(_AutocompleteBuilder, "AutocompleteBuilder");
3275
3657
  var AutocompleteBuilder = _AutocompleteBuilder;
3276
3658
  var _SimilarBuilder = class _SimilarBuilder {
3277
- constructor(httpClient, sourceId) {
3659
+ /**
3660
+ * @param locale - The client's locale (`withLocale()`, `updateConfig({ locale })`), sent when
3661
+ * `.locale()` names none: the function `TypedRelyIOClient` hands over, or a locale fixed for it
3662
+ */
3663
+ constructor(httpClient, sourceId, locale) {
3278
3664
  this.httpClient = httpClient;
3279
3665
  chunkMWMSFIWN_cjs.__publicField(this, "sourceId");
3280
3666
  chunkMWMSFIWN_cjs.__publicField(this, "similarOptions", {});
3667
+ chunkMWMSFIWN_cjs.__publicField(this, "requestHeaders");
3668
+ /** The client's locale, asked when the read is sent rather than copied when it was built. */
3669
+ chunkMWMSFIWN_cjs.__publicField(this, "clientLocale");
3281
3670
  this.sourceId = sourceId;
3671
+ this.clientLocale = readClientLocale(locale);
3672
+ }
3673
+ /**
3674
+ * Headers this read adds to the client's own, e.g. `X-RelyIO-Dataset` to read another dataset
3675
+ * the key can see. A name the client already sends is replaced for this read only.
3676
+ */
3677
+ headers(headers) {
3678
+ this.requestHeaders = headers;
3679
+ return this;
3282
3680
  }
3283
3681
  /**
3284
3682
  * Find similar content within a content type
@@ -3286,19 +3684,24 @@ var _SimilarBuilder = class _SimilarBuilder {
3286
3684
  */
3287
3685
  in(contentType) {
3288
3686
  this.similarOptions.contentType = contentType;
3687
+ this.similarOptions.contentTypes = void 0;
3289
3688
  return this;
3290
3689
  }
3291
3690
  /**
3292
- * Add filter conditions
3293
- * @param conditions - Filter conditions
3691
+ * Find similar content across several content types, or `'*'` for every type
3692
+ * @param contentTypes - Array of content types or '*' for all
3294
3693
  */
3295
- where(conditions) {
3296
- this.similarOptions.where = {
3297
- ...this.similarOptions.where,
3298
- ...conditions
3299
- };
3694
+ across(contentTypes) {
3695
+ this.similarOptions.contentTypes = contentTypes;
3696
+ this.similarOptions.contentType = void 0;
3300
3697
  return this;
3301
3698
  }
3699
+ /*
3700
+ * No `where()` (REL-1452). Similar ranks from the source entry's stored vector, and the vector
3701
+ * index can pre-filter by content type and locale only — never by an entry field. The API used to
3702
+ * accept `where` here and apply nothing; it now refuses the property, so the builder does not
3703
+ * offer a filter that cannot be honoured. Narrow with `.in()`, `.across()` and `.locale()`.
3704
+ */
3302
3705
  /**
3303
3706
  * Limit number of results
3304
3707
  * @param count - Maximum results
@@ -3319,26 +3722,30 @@ var _SimilarBuilder = class _SimilarBuilder {
3319
3722
  * Execute the similar query
3320
3723
  */
3321
3724
  async execute() {
3322
- if (this.similarOptions.contentType == null || this.similarOptions.contentType === "") {
3323
- throw new Error('Content type is required for similar search. Use .in("contentType")');
3725
+ const { contentType, contentTypes } = this.similarOptions;
3726
+ if ((contentType == null || contentType === "") && contentTypes == null) {
3727
+ throw new Error(
3728
+ 'Content type is required for similar search. Use .in("contentType") or .across("*")'
3729
+ );
3324
3730
  }
3325
- const body = {
3326
- source: this.sourceId,
3327
- contentType: this.similarOptions.contentType
3328
- };
3329
- if (this.similarOptions.where && Object.keys(this.similarOptions.where).length > 0) {
3330
- body.where = this.similarOptions.where;
3731
+ const body = { source: this.sourceId };
3732
+ if (contentType != null && contentType !== "") {
3733
+ body.contentType = contentType;
3734
+ }
3735
+ if (contentTypes != null) {
3736
+ body.contentTypes = contentTypes;
3331
3737
  }
3332
3738
  if (this.similarOptions.limit !== void 0) {
3333
3739
  body.limit = this.similarOptions.limit;
3334
3740
  }
3335
- if (this.similarOptions.locale != null && this.similarOptions.locale !== "") {
3336
- body.locale = this.similarOptions.locale;
3741
+ const locale = this.similarOptions.locale ?? this.clientLocale();
3742
+ if (locale != null && locale !== "") {
3743
+ body.locale = locale;
3337
3744
  }
3338
3745
  const response = await this.httpClient.send(
3339
3746
  "/v1/search/similar",
3340
3747
  "post",
3341
- { body }
3748
+ { body, headers: this.requestHeaders }
3342
3749
  );
3343
3750
  return response.body;
3344
3751
  }
@@ -3358,37 +3765,37 @@ var _SimilarBuilder = class _SimilarBuilder {
3358
3765
  chunkMWMSFIWN_cjs.__name(_SimilarBuilder, "SimilarBuilder");
3359
3766
  var SimilarBuilder = _SimilarBuilder;
3360
3767
  var _SearchClient = class _SearchClient {
3361
- constructor(httpClient) {
3768
+ /**
3769
+ * @param locale - The client's locale (`withLocale()`, `updateConfig({ locale })`): every builder
3770
+ * here sends it when the query names none, as `content()`'s reads do — the function
3771
+ * `TypedRelyIOClient` hands over, or a locale fixed for this client
3772
+ */
3773
+ constructor(httpClient, locale) {
3362
3774
  this.httpClient = httpClient;
3775
+ /** The client's locale, handed to every builder here to ask when it sends. */
3776
+ chunkMWMSFIWN_cjs.__publicField(this, "clientLocale");
3777
+ this.clientLocale = readClientLocale(locale);
3363
3778
  }
3364
3779
  /**
3365
3780
  * Start a search query
3366
3781
  * @param query - Search query string
3367
3782
  */
3368
3783
  search(query) {
3369
- return new SearchBuilder(this.httpClient, query);
3784
+ return new SearchBuilder(this.httpClient, query, this.clientLocale);
3370
3785
  }
3371
3786
  /**
3372
3787
  * Start an autocomplete query
3373
3788
  * @param query - Partial query to autocomplete
3374
3789
  */
3375
3790
  autocomplete(query) {
3376
- return new AutocompleteBuilder(this.httpClient, query);
3791
+ return new AutocompleteBuilder(this.httpClient, query, this.clientLocale);
3377
3792
  }
3378
3793
  /**
3379
3794
  * Find content similar to a given entry
3380
3795
  * @param sourceId - Entry ID to find similar content for
3381
3796
  */
3382
3797
  similar(sourceId) {
3383
- return new SimilarBuilder(this.httpClient, sourceId);
3384
- }
3385
- /**
3386
- * Quick search helper - searches all content types
3387
- * @param query - Search query
3388
- * @param limit - Max results (default 20)
3389
- */
3390
- async global(query, limit = 20) {
3391
- return this.search(query).across("*").limit(limit).execute();
3798
+ return new SimilarBuilder(this.httpClient, sourceId, this.clientLocale);
3392
3799
  }
3393
3800
  };
3394
3801
  chunkMWMSFIWN_cjs.__name(_SearchClient, "SearchClient");
@@ -3396,7 +3803,12 @@ var SearchClient = _SearchClient;
3396
3803
 
3397
3804
  // src/batch/batch-client.ts
3398
3805
  var _BatchBuilder = class _BatchBuilder {
3399
- constructor(httpClient) {
3806
+ /**
3807
+ * @param locale - The client's locale (`withLocale()`, `updateConfig({ locale })`), each
3808
+ * operation's when it names none: the function `TypedRelyIOClient` hands over, or a locale fixed
3809
+ * for this batch
3810
+ */
3811
+ constructor(httpClient, locale) {
3400
3812
  this.httpClient = httpClient;
3401
3813
  chunkMWMSFIWN_cjs.__publicField(this, "operations", []);
3402
3814
  chunkMWMSFIWN_cjs.__publicField(this, "defaultOptions", {
@@ -3405,6 +3817,18 @@ var _BatchBuilder = class _BatchBuilder {
3405
3817
  returnData: true,
3406
3818
  continueOnError: true
3407
3819
  });
3820
+ chunkMWMSFIWN_cjs.__publicField(this, "requestHeaders");
3821
+ /** The client's locale, asked as each operation is added rather than copied when this was built. */
3822
+ chunkMWMSFIWN_cjs.__publicField(this, "clientLocale");
3823
+ this.clientLocale = readClientLocale(locale);
3824
+ }
3825
+ /**
3826
+ * An operation's body with its locale: the one it names, else the client's as it is when the
3827
+ * operation is added.
3828
+ */
3829
+ localized(data) {
3830
+ const locale = callLocale(data.locale, this.clientLocale());
3831
+ return locale === void 0 ? data : { ...data, locale };
3408
3832
  }
3409
3833
  // ==========================================================================
3410
3834
  // ENTRY OPERATIONS
@@ -3420,7 +3844,7 @@ var _BatchBuilder = class _BatchBuilder {
3420
3844
  id,
3421
3845
  method: "POST",
3422
3846
  path: `/${contentType}`,
3423
- body: data
3847
+ body: this.localized(data)
3424
3848
  });
3425
3849
  return this;
3426
3850
  }
@@ -3440,7 +3864,7 @@ var _BatchBuilder = class _BatchBuilder {
3440
3864
  id,
3441
3865
  method: "PATCH",
3442
3866
  path: `/${contentType}/${entryId}`,
3443
- body: data
3867
+ body: this.localized(data)
3444
3868
  });
3445
3869
  return this;
3446
3870
  }
@@ -3460,7 +3884,7 @@ var _BatchBuilder = class _BatchBuilder {
3460
3884
  id,
3461
3885
  method: "PUT",
3462
3886
  path: `/${contentType}/${entryId}`,
3463
- body: data
3887
+ body: this.localized(data)
3464
3888
  });
3465
3889
  return this;
3466
3890
  }
@@ -3492,7 +3916,7 @@ var _BatchBuilder = class _BatchBuilder {
3492
3916
  id,
3493
3917
  method: "PATCH",
3494
3918
  path: `/_singletons/${singletonKey}`,
3495
- body: data
3919
+ body: this.localized(data)
3496
3920
  });
3497
3921
  return this;
3498
3922
  }
@@ -3507,7 +3931,7 @@ var _BatchBuilder = class _BatchBuilder {
3507
3931
  id,
3508
3932
  method: "POST",
3509
3933
  path: `/_singletons/${singletonKey}`,
3510
- body: data
3934
+ body: this.localized(data)
3511
3935
  });
3512
3936
  return this;
3513
3937
  }
@@ -3515,7 +3939,8 @@ var _BatchBuilder = class _BatchBuilder {
3515
3939
  // RAW OPERATIONS
3516
3940
  // ==========================================================================
3517
3941
  /**
3518
- * Add a raw operation (for advanced use cases)
3942
+ * Add a raw operation (for advanced use cases). It is sent exactly as written: the client's
3943
+ * locale is not added to it.
3519
3944
  * @param operation - The operation to add
3520
3945
  */
3521
3946
  raw(operation) {
@@ -3606,6 +4031,15 @@ var _BatchBuilder = class _BatchBuilder {
3606
4031
  this.defaultOptions.continueOnError = false;
3607
4032
  return this;
3608
4033
  }
4034
+ /**
4035
+ * Headers the batch request adds — e.g. `{ 'X-RelyIO-Dataset': 'staging' }` to write a dataset
4036
+ * other than the client's. They replace the client's own header of the same name for this
4037
+ * request alone, and apply to every operation in it: a batch is one request.
4038
+ */
4039
+ headers(headers) {
4040
+ this.requestHeaders = { ...this.requestHeaders, ...headers };
4041
+ return this;
4042
+ }
3609
4043
  // ==========================================================================
3610
4044
  // EXECUTION
3611
4045
  // ==========================================================================
@@ -3632,9 +4066,10 @@ var _BatchBuilder = class _BatchBuilder {
3632
4066
  * Clone the builder
3633
4067
  */
3634
4068
  clone() {
3635
- const cloned = new _BatchBuilder(this.httpClient);
4069
+ const cloned = new _BatchBuilder(this.httpClient, this.clientLocale);
3636
4070
  cloned.operations = [...this.operations];
3637
4071
  cloned.defaultOptions = { ...this.defaultOptions };
4072
+ cloned.requestHeaders = this.requestHeaders === void 0 ? void 0 : { ...this.requestHeaders };
3638
4073
  return cloned;
3639
4074
  }
3640
4075
  /**
@@ -3652,7 +4087,8 @@ var _BatchBuilder = class _BatchBuilder {
3652
4087
  }
3653
4088
  const finalOptions = { ...this.defaultOptions, ...options };
3654
4089
  return this.httpClient.fetchPayload("/v1/batch", "post", {
3655
- body: { operations: this.operations, options: finalOptions }
4090
+ body: { operations: this.operations, options: finalOptions },
4091
+ headers: this.requestHeaders
3656
4092
  });
3657
4093
  }
3658
4094
  /**
@@ -3683,14 +4119,21 @@ var _BatchBuilder = class _BatchBuilder {
3683
4119
  chunkMWMSFIWN_cjs.__name(_BatchBuilder, "BatchBuilder");
3684
4120
  var BatchBuilder = _BatchBuilder;
3685
4121
  var _BatchClient = class _BatchClient {
3686
- constructor(httpClient) {
4122
+ /**
4123
+ * @param locale - The client's locale: the function `TypedRelyIOClient` hands over, or a locale
4124
+ * fixed for this client
4125
+ */
4126
+ constructor(httpClient, locale) {
3687
4127
  this.httpClient = httpClient;
4128
+ /** The client's locale, handed to every batch this builds to ask as operations are added. */
4129
+ chunkMWMSFIWN_cjs.__publicField(this, "clientLocale");
4130
+ this.clientLocale = readClientLocale(locale);
3688
4131
  }
3689
4132
  /**
3690
- * Create a new batch builder
4133
+ * Create a new batch builder, whose operations default to this client's locale
3691
4134
  */
3692
4135
  create() {
3693
- return new BatchBuilder(this.httpClient);
4136
+ return new BatchBuilder(this.httpClient, this.clientLocale);
3694
4137
  }
3695
4138
  /**
3696
4139
  * Quick bulk create entries
@@ -3732,12 +4175,27 @@ chunkMWMSFIWN_cjs.__name(_BatchClient, "BatchClient");
3732
4175
  var BatchClient = _BatchClient;
3733
4176
 
3734
4177
  // src/queries/adhoc-query-client.ts
4178
+ function withClientLocale(parameters, clientLocale) {
4179
+ if (clientLocale === void 0 || clientLocale === "" || parameters?.locale != null) {
4180
+ return parameters;
4181
+ }
4182
+ return { ...parameters, locale: clientLocale };
4183
+ }
4184
+ chunkMWMSFIWN_cjs.__name(withClientLocale, "withClientLocale");
3735
4185
  var _AdhocQueryBuilder = class _AdhocQueryBuilder {
3736
- constructor(httpClient) {
4186
+ /**
4187
+ * @param locale - The client's locale (`withLocale()`, `updateConfig({ locale })`), sent as the
4188
+ * `locale` parameter when `.param('locale', …)` binds none: the function `TypedRelyIOClient`
4189
+ * hands over, or a locale fixed for this builder
4190
+ */
4191
+ constructor(httpClient, locale) {
3737
4192
  this.httpClient = httpClient;
3738
4193
  chunkMWMSFIWN_cjs.__publicField(this, "queryDef", {});
3739
4194
  chunkMWMSFIWN_cjs.__publicField(this, "parameters", {});
3740
4195
  chunkMWMSFIWN_cjs.__publicField(this, "options", {});
4196
+ /** The client's locale, asked when the query is sent rather than copied when it was built. */
4197
+ chunkMWMSFIWN_cjs.__publicField(this, "clientLocale");
4198
+ this.clientLocale = readClientLocale(locale);
3741
4199
  }
3742
4200
  // ==========================================================================
3743
4201
  // QUERY BLOCKS
@@ -3782,14 +4240,6 @@ var _AdhocQueryBuilder = class _AdhocQueryBuilder {
3782
4240
  this.queryDef.search = config;
3783
4241
  return this;
3784
4242
  }
3785
- /**
3786
- * Add similar block
3787
- * @param config - Similar configuration
3788
- */
3789
- similar(config) {
3790
- this.queryDef.similar = config;
3791
- return this;
3792
- }
3793
4243
  // ==========================================================================
3794
4244
  // FETCH MODIFIERS
3795
4245
  // ==========================================================================
@@ -3966,16 +4416,17 @@ var _AdhocQueryBuilder = class _AdhocQueryBuilder {
3966
4416
  * Execute the query
3967
4417
  */
3968
4418
  async execute() {
3969
- if (!this.queryDef.fetch && !this.queryDef.aggregate && !this.queryDef.search && !this.queryDef.similar) {
4419
+ if (!this.queryDef.fetch && !this.queryDef.aggregate && !this.queryDef.search) {
3970
4420
  throw new Error(
3971
- "Query must have at least one of: fetch, aggregate, search, or similar. Use .from(), .aggregate(), .search(), or .similar()"
4421
+ "Query must have at least one of: fetch, aggregate or search. Use .from(), .aggregate() or .search()"
3972
4422
  );
3973
4423
  }
3974
4424
  const body = {
3975
4425
  query: this.queryDef
3976
4426
  };
3977
- if (Object.keys(this.parameters).length > 0) {
3978
- body.parameters = this.parameters;
4427
+ const parameters = withClientLocale(this.parameters, this.clientLocale());
4428
+ if (parameters !== void 0 && Object.keys(parameters).length > 0) {
4429
+ body.parameters = parameters;
3979
4430
  }
3980
4431
  if (Object.keys(this.options).length > 0) {
3981
4432
  body.options = this.options;
@@ -4006,7 +4457,7 @@ var _AdhocQueryBuilder = class _AdhocQueryBuilder {
4006
4457
  * Clone the builder
4007
4458
  */
4008
4459
  clone() {
4009
- const cloned = new _AdhocQueryBuilder(this.httpClient);
4460
+ const cloned = new _AdhocQueryBuilder(this.httpClient, this.clientLocale);
4010
4461
  cloned.queryDef = JSON.parse(JSON.stringify(this.queryDef));
4011
4462
  cloned.parameters = { ...this.parameters };
4012
4463
  cloned.options = { ...this.options };
@@ -4016,27 +4467,39 @@ var _AdhocQueryBuilder = class _AdhocQueryBuilder {
4016
4467
  chunkMWMSFIWN_cjs.__name(_AdhocQueryBuilder, "AdhocQueryBuilder");
4017
4468
  var AdhocQueryBuilder = _AdhocQueryBuilder;
4018
4469
  var _AdhocQueryClient = class _AdhocQueryClient {
4019
- constructor(httpClient) {
4470
+ /**
4471
+ * @param locale - The client's locale (`withLocale()`, `updateConfig({ locale })`), sent as the
4472
+ * `locale` parameter of every query that binds none: the function `TypedRelyIOClient` hands
4473
+ * over, or a locale fixed for this client
4474
+ */
4475
+ constructor(httpClient, locale) {
4020
4476
  this.httpClient = httpClient;
4477
+ /** The client's locale, asked on every query rather than copied when this client was taken. */
4478
+ chunkMWMSFIWN_cjs.__publicField(this, "clientLocale");
4479
+ this.clientLocale = readClientLocale(locale);
4021
4480
  }
4022
4481
  /**
4023
4482
  * Create a new query builder
4024
4483
  */
4025
4484
  build() {
4026
- return new AdhocQueryBuilder(this.httpClient);
4485
+ return new AdhocQueryBuilder(this.httpClient, this.clientLocale);
4027
4486
  }
4028
4487
  /**
4029
4488
  * Execute a query definition directly
4030
4489
  * @param definition - Query definition
4031
4490
  * @param parameters - Parameter values
4032
4491
  * @param options - Execution options
4492
+ * @param request - `headers` this one request adds to the client's own, e.g. `X-RelyIO-Dataset`
4493
+ * to query another dataset the key can see. A name the client already sends is replaced for
4494
+ * this request only.
4033
4495
  */
4034
- async execute(definition, parameters, options) {
4496
+ async execute(definition, parameters, options, request) {
4035
4497
  const body = {
4036
4498
  query: definition
4037
4499
  };
4038
- if (parameters && Object.keys(parameters).length > 0) {
4039
- body.parameters = parameters;
4500
+ const sent = withClientLocale(parameters, this.clientLocale());
4501
+ if (sent !== void 0 && Object.keys(sent).length > 0) {
4502
+ body.parameters = sent;
4040
4503
  }
4041
4504
  if (options && Object.keys(options).length > 0) {
4042
4505
  body.options = options;
@@ -4044,7 +4507,7 @@ var _AdhocQueryClient = class _AdhocQueryClient {
4044
4507
  return this.httpClient.fetchPayload(
4045
4508
  "/v1/query",
4046
4509
  "post",
4047
- { body }
4510
+ { body, headers: request?.headers }
4048
4511
  );
4049
4512
  }
4050
4513
  /**
@@ -4108,12 +4571,12 @@ async function* pollChanges(http, options = {}) {
4108
4571
  let since = options.since;
4109
4572
  let resynced;
4110
4573
  for (; ; ) {
4111
- if (aborted(signal)) return;
4574
+ if (isAborted(signal)) return;
4112
4575
  let response;
4113
4576
  try {
4114
4577
  response = await pollOnce(http, since, options);
4115
4578
  } catch (error) {
4116
- if (aborted(signal)) return;
4579
+ if (isAborted(signal)) return;
4117
4580
  if (!(error instanceof ResourceExpiredError) || since === void 0) throw error;
4118
4581
  const expired = ChangesExpiredError.of(error);
4119
4582
  const onExpired = options.onExpired ?? "throw";
@@ -4131,11 +4594,11 @@ async function* pollChanges(http, options = {}) {
4131
4594
  const expired = resynced;
4132
4595
  resynced = void 0;
4133
4596
  await options.onExpired(expired);
4134
- if (aborted(signal)) return;
4597
+ if (isAborted(signal)) return;
4135
4598
  }
4136
4599
  for (const change of data) {
4137
4600
  yield change;
4138
- if (aborted(signal)) return;
4601
+ if (isAborted(signal)) return;
4139
4602
  }
4140
4603
  if (data.length === 0) {
4141
4604
  const wait = parseRetryAfterMs(headerValue(response.headers, "Retry-After"), Date.now());
@@ -4144,6 +4607,21 @@ async function* pollChanges(http, options = {}) {
4144
4607
  }
4145
4608
  }
4146
4609
  chunkMWMSFIWN_cjs.__name(pollChanges, "pollChanges");
4610
+ async function readChangesHead(http, options = {}) {
4611
+ const response = await http.send("/v1/changes", "get", {
4612
+ query: {},
4613
+ ...options.signal === void 0 ? {} : { signal: options.signal }
4614
+ });
4615
+ const { data, page } = response.body;
4616
+ if (page.nextCursor === null) {
4617
+ throw new Error("GET /v1/changes answered without page.nextCursor");
4618
+ }
4619
+ if (data.length > 0) {
4620
+ throw new Error("GET /v1/changes without since answered changes; a head carries none");
4621
+ }
4622
+ return page.nextCursor;
4623
+ }
4624
+ chunkMWMSFIWN_cjs.__name(readChangesHead, "readChangesHead");
4147
4625
  async function pollOnce(http, since, options) {
4148
4626
  const query = {};
4149
4627
  if (since !== void 0) query.since = since;
@@ -4157,26 +4635,6 @@ async function pollOnce(http, since, options) {
4157
4635
  });
4158
4636
  }
4159
4637
  chunkMWMSFIWN_cjs.__name(pollOnce, "pollOnce");
4160
- function sleep(ms, signal) {
4161
- return new Promise((resolve) => {
4162
- if (aborted(signal)) {
4163
- resolve();
4164
- return;
4165
- }
4166
- const done = /* @__PURE__ */ chunkMWMSFIWN_cjs.__name(() => {
4167
- clearTimeout(timer);
4168
- signal?.removeEventListener("abort", done);
4169
- resolve();
4170
- }, "done");
4171
- const timer = setTimeout(done, ms);
4172
- signal?.addEventListener("abort", done, { once: true });
4173
- });
4174
- }
4175
- chunkMWMSFIWN_cjs.__name(sleep, "sleep");
4176
- function aborted(signal) {
4177
- return signal?.aborted === true;
4178
- }
4179
- chunkMWMSFIWN_cjs.__name(aborted, "aborted");
4180
4638
 
4181
4639
  // src/config/dev-detection.ts
4182
4640
  function sandboxStateOf(resolution) {
@@ -4288,7 +4746,7 @@ var DEFAULT_CONFIG = {
4288
4746
  environment: "production",
4289
4747
  headers: {},
4290
4748
  browser: {
4291
- includeCredentials: true,
4749
+ includeCredentials: false,
4292
4750
  useRelativeUrls: false
4293
4751
  },
4294
4752
  cache: {
@@ -4314,54 +4772,48 @@ var DEFAULT_CONFIG = {
4314
4772
  }
4315
4773
  };
4316
4774
  function validateConfig(config) {
4317
- if (!config.apiKey || config.apiKey.length === 0) {
4318
- throw new Error("API key is required");
4319
- }
4320
- if (typeof config.apiKey !== "string") {
4775
+ if (config.apiKey != null && typeof config.apiKey !== "string") {
4321
4776
  throw new Error("API key must be a string");
4322
4777
  }
4323
4778
  if (config.baseUrl != null && typeof config.baseUrl !== "string") {
4324
4779
  throw new Error("Base URL must be a string");
4325
4780
  }
4326
- if (config.timeout != null && (typeof config.timeout !== "number" || config.timeout < 0)) {
4327
- throw new Error("Timeout must be a positive number");
4328
- }
4329
- if (config.retryAttempts != null && (typeof config.retryAttempts !== "number" || config.retryAttempts < 0)) {
4330
- throw new Error("Retry attempts must be a positive number");
4781
+ if (config.timeout != null) assertRequestTimeout(config.timeout);
4782
+ if (config.retryAttempts != null && !(Number.isInteger(config.retryAttempts) && config.retryAttempts >= 1)) {
4783
+ throw new Error(
4784
+ `retryAttempts must be a whole number of at least 1 \u2014 the most attempts one request makes, the first included, so 1 turns retrying off \u2014 and got ${String(config.retryAttempts)}`
4785
+ );
4331
4786
  }
4332
4787
  }
4333
4788
  chunkMWMSFIWN_cjs.__name(validateConfig, "validateConfig");
4334
4789
  var devOverrideNotified = false;
4335
4790
  function resolveConfig(userConfig) {
4336
4791
  validateConfig(userConfig);
4337
- const merged = {
4338
- ...DEFAULT_CONFIG,
4339
- ...userConfig,
4340
- headers: {
4341
- ...DEFAULT_CONFIG.headers,
4342
- ...userConfig.headers
4343
- },
4344
- browser: {
4345
- ...DEFAULT_CONFIG.browser,
4346
- ...userConfig.browser
4347
- },
4348
- cache: {
4349
- ...DEFAULT_CONFIG.cache,
4350
- ...userConfig.cache
4351
- },
4352
- realtime: {
4353
- ...DEFAULT_CONFIG.realtime,
4354
- ...userConfig.realtime
4355
- },
4356
- debug: {
4357
- ...DEFAULT_CONFIG.debug,
4358
- ...userConfig.debug
4359
- }
4360
- };
4361
- const dev = resolveDevOverride(userConfig.dev);
4792
+ return withHandshake(withDefaults(DEFAULT_CONFIG, userConfig, DEFAULT_CONFIG));
4793
+ }
4794
+ chunkMWMSFIWN_cjs.__name(resolveConfig, "resolveConfig");
4795
+ function resolveConfigUpdate(configured, update) {
4796
+ validateConfig(update);
4797
+ const base = update.apiKey === void 0 ? configured : withoutKeyHeader(configured);
4798
+ return withHandshake(withDefaults(base, update, DEFAULT_CONFIG));
4799
+ }
4800
+ chunkMWMSFIWN_cjs.__name(resolveConfigUpdate, "resolveConfigUpdate");
4801
+ function withConfiguredKey(configured, apiKey) {
4802
+ return { ...withoutKeyHeader(configured), apiKey };
4803
+ }
4804
+ chunkMWMSFIWN_cjs.__name(withConfiguredKey, "withConfiguredKey");
4805
+ function withoutKeyHeader(configured) {
4806
+ return { ...configured, headers: layerHeaders(configured.headers, { [API_KEY_HEADER]: null }) };
4807
+ }
4808
+ chunkMWMSFIWN_cjs.__name(withoutKeyHeader, "withoutKeyHeader");
4809
+ function withHandshake(configured) {
4810
+ const dev = resolveDevOverride(configured.dev);
4811
+ const merged = withDefaults(
4812
+ configured,
4813
+ dev.state === "active" ? { apiKey: dev.apiKey, dataset: dev.sandbox.datasetId } : {},
4814
+ DEFAULT_CONFIG
4815
+ );
4362
4816
  if (dev.state === "active") {
4363
- merged.apiKey = dev.apiKey;
4364
- merged.dataset = dev.sandbox.datasetId;
4365
4817
  if (!devOverrideNotified) {
4366
4818
  devOverrideNotified = true;
4367
4819
  console.info(
@@ -4379,30 +4831,67 @@ function resolveConfig(userConfig) {
4379
4831
  `[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.`
4380
4832
  );
4381
4833
  }
4382
- return { config: merged, sandbox: sandboxStateOf(dev) };
4834
+ return { config: merged, sandbox: sandboxStateOf(dev), configured };
4383
4835
  }
4384
- chunkMWMSFIWN_cjs.__name(resolveConfig, "resolveConfig");
4836
+ chunkMWMSFIWN_cjs.__name(withHandshake, "withHandshake");
4837
+ function withDefaults(base, given, defaults) {
4838
+ const merged = new Map(Object.entries(base));
4839
+ const givenValues = new Map(Object.entries(given ?? {}));
4840
+ const defaultValues = new Map(Object.entries(defaults));
4841
+ for (const key of /* @__PURE__ */ new Set([...merged.keys(), ...givenValues.keys()])) {
4842
+ const fallback = merged.get(key);
4843
+ const value = givenValues.get(key);
4844
+ const givenBag = isOptionBag(value) ? value : void 0;
4845
+ if (isOptionBag(fallback) && key === "headers") {
4846
+ merged.set(key, layerHeaders(fallback, givenBag));
4847
+ } else if (isOptionBag(fallback)) {
4848
+ const defaultBag = defaultValues.get(key);
4849
+ merged.set(key, withDefaults(fallback, givenBag, isOptionBag(defaultBag) ? defaultBag : {}));
4850
+ } else if (value === null) {
4851
+ if (defaultValues.has(key)) merged.set(key, defaultValues.get(key));
4852
+ else merged.delete(key);
4853
+ } else if (value !== void 0) {
4854
+ merged.set(key, value);
4855
+ }
4856
+ }
4857
+ return Object.fromEntries(merged);
4858
+ }
4859
+ chunkMWMSFIWN_cjs.__name(withDefaults, "withDefaults");
4860
+ function isOptionBag(value) {
4861
+ return typeof value === "object" && value !== null && !Array.isArray(value);
4862
+ }
4863
+ chunkMWMSFIWN_cjs.__name(isOptionBag, "isOptionBag");
4385
4864
 
4386
4865
  // src/client/create-client.ts
4387
4866
  var API_VERSION_SEGMENT = "v1";
4388
4867
  var _TypedRelyIOClient = class _TypedRelyIOClient {
4389
4868
  constructor(config) {
4869
+ /**
4870
+ * The one HTTP client every sub-client sends through. `updateConfig()` and `setApiKey()` change it
4871
+ * in place rather than replacing it, so a sub-client taken before either sends through the change.
4872
+ */
4390
4873
  chunkMWMSFIWN_cjs.__publicField(this, "httpClient");
4391
4874
  chunkMWMSFIWN_cjs.__publicField(this, "config");
4875
+ /**
4876
+ * The configuration as `createClient()`, `setApiKey()` and `updateConfig()` gave it, before the
4877
+ * `relyio dev` handshake: what the next update is laid over, and what `withLocale()` starts from.
4878
+ */
4879
+ chunkMWMSFIWN_cjs.__publicField(this, "configured");
4392
4880
  chunkMWMSFIWN_cjs.__publicField(this, "_sandbox");
4393
- chunkMWMSFIWN_cjs.__publicField(this, "_search");
4394
- chunkMWMSFIWN_cjs.__publicField(this, "_batch");
4395
- chunkMWMSFIWN_cjs.__publicField(this, "_adhocQuery");
4396
4881
  chunkMWMSFIWN_cjs.__publicField(this, "_schema");
4882
+ /**
4883
+ * The client's locale as it is when a call is made. Every sub-client and builder is handed this
4884
+ * rather than a copy of the locale, so one taken before `updateConfig({ locale })` sends the new
4885
+ * locale, or none after `locale: null` (REL-1453).
4886
+ */
4887
+ chunkMWMSFIWN_cjs.__publicField(this, "currentLocale", /* @__PURE__ */ chunkMWMSFIWN_cjs.__name(() => this.config.locale, "currentLocale"));
4397
4888
  const { schema, ...clientConfig } = config;
4398
4889
  this._schema = schema;
4399
4890
  const resolved = resolveConfig(clientConfig);
4400
4891
  this.config = resolved.config;
4892
+ this.configured = resolved.configured;
4401
4893
  this._sandbox = resolved.sandbox;
4402
4894
  this.httpClient = new HttpClient(this.config);
4403
- this._search = new SearchClient(this.httpClient);
4404
- this._batch = new BatchClient(this.httpClient);
4405
- this._adhocQuery = new AdhocQueryClient(this.httpClient);
4406
4895
  }
4407
4896
  // ============================================================================
4408
4897
  // CORE CLIENTS
@@ -4431,10 +4920,10 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
4431
4920
  return this._sandbox;
4432
4921
  }
4433
4922
  /**
4434
- * Get the ad-hoc query client
4923
+ * Get the ad-hoc query client. Every query it sends is in the client's locale as it is then.
4435
4924
  */
4436
4925
  get query() {
4437
- return this._adhocQuery;
4926
+ return new AdhocQueryClient(this.httpClient, this.currentLocale);
4438
4927
  }
4439
4928
  // ============================================================================
4440
4929
  // CONTENT OPERATIONS
@@ -4454,7 +4943,7 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
4454
4943
  * ```
4455
4944
  */
4456
4945
  content(contentType) {
4457
- return new ContentClient(this.httpClient, contentType, this.config.locale);
4946
+ return new ContentClient(this.httpClient, contentType, this.currentLocale);
4458
4947
  }
4459
4948
  /**
4460
4949
  * Get an asset client for a specific asset type.
@@ -4475,7 +4964,7 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
4475
4964
  * ```
4476
4965
  */
4477
4966
  asset(assetType) {
4478
- return new AssetClient(this.httpClient, assetType, this.config.locale);
4967
+ return new AssetClient(this.httpClient, assetType, this.currentLocale);
4479
4968
  }
4480
4969
  /**
4481
4970
  * Get a singleton client for a specific singleton key
@@ -4490,7 +4979,7 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
4490
4979
  * ```
4491
4980
  */
4492
4981
  singleton(key) {
4493
- return new SingletonClient(this.httpClient, key);
4982
+ return new SingletonClient(this.httpClient, key, this.currentLocale);
4494
4983
  }
4495
4984
  /**
4496
4985
  * Get a settings client for a specific settings group
@@ -4504,13 +4993,16 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
4504
4993
  * ```
4505
4994
  */
4506
4995
  settings(groupId) {
4507
- return new SettingsClient(this.httpClient, groupId);
4996
+ return new SettingsClient(this.httpClient, groupId, this.currentLocale);
4508
4997
  }
4509
4998
  // ============================================================================
4510
4999
  // SEARCH OPERATIONS
4511
5000
  // ============================================================================
4512
5001
  /**
4513
- * Start a search query with full-text, semantic, or hybrid search
5002
+ * Start a search query with full-text, semantic, or hybrid search.
5003
+ *
5004
+ * Where a schema types the client, the results are its entries — every content type's until
5005
+ * `in()` or `across()` narrows them (`INV-SS-TYPES-INFERRED`).
4514
5006
  *
4515
5007
  * @example
4516
5008
  * ```typescript
@@ -4523,7 +5015,7 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
4523
5015
  * ```
4524
5016
  */
4525
5017
  search(query) {
4526
- return this._search.search(query);
5018
+ return this.searchClient().search(query);
4527
5019
  }
4528
5020
  /**
4529
5021
  * Start an autocomplete query for search suggestions
@@ -4539,7 +5031,7 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
4539
5031
  * ```
4540
5032
  */
4541
5033
  autocomplete(query) {
4542
- return this._search.autocomplete(query);
5034
+ return this.searchClient().autocomplete(query);
4543
5035
  }
4544
5036
  /**
4545
5037
  * Find content similar to a given entry using vector search
@@ -4554,13 +5046,17 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
4554
5046
  * ```
4555
5047
  */
4556
5048
  similar(sourceId) {
4557
- return this._search.similar(sourceId);
5049
+ return this.searchClient().similar(sourceId);
4558
5050
  }
4559
5051
  // ============================================================================
4560
5052
  // BATCH OPERATIONS
4561
5053
  // ============================================================================
4562
5054
  /**
4563
- * Start a batch operation for bulk create/update/delete
5055
+ * Start a batch operation for bulk create/update/delete.
5056
+ *
5057
+ * By default each operation lands or fails on its own. `atomic: true` makes the batch one
5058
+ * transaction, which the API accepts only when every operation is the same kind of write and none
5059
+ * writes a singleton that does not exist yet.
4564
5060
  *
4565
5061
  * @example
4566
5062
  * ```typescript
@@ -4569,13 +5065,23 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
4569
5065
  * .update('product', 'entry_abc', { content: { price: 899 } })
4570
5066
  * .delete('product', 'entry_xyz')
4571
5067
  * .upsertSingleton('homepage', { content: { hero: 'Welcome!' } })
4572
- * .execute({ atomic: true })
5068
+ * .execute()
4573
5069
  *
4574
5070
  * console.log(result.summary.succeeded)
5071
+ *
5072
+ * // All or nothing: one kind of write
5073
+ * await relyio.batch()
5074
+ * .update('product', 'entry_abc', { content: { price: 899 } })
5075
+ * .update('product', 'entry_def', { content: { price: 799 } })
5076
+ * .execute({ atomic: true })
4575
5077
  * ```
4576
5078
  */
4577
5079
  batch() {
4578
- return this._batch.create();
5080
+ return new BatchClient(this.httpClient, this.currentLocale).create();
5081
+ }
5082
+ /** The search client, whose builders send the client's locale as it is when each one is sent. */
5083
+ searchClient() {
5084
+ return new SearchClient(this.httpClient, this.currentLocale);
4579
5085
  }
4580
5086
  // ============================================================================
4581
5087
  // CHANGES FEED
@@ -4608,6 +5114,25 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
4608
5114
  changes(options = {}) {
4609
5115
  return pollChanges(this.httpClient, options);
4610
5116
  }
5117
+ /**
5118
+ * The dataset's current position in the changes feed, as a cursor to start
5119
+ * `changes({ since })` or `followChanges(…, { since })` from. One
5120
+ * `GET /v1/changes` without a cursor: answered at once, holding nothing.
5121
+ *
5122
+ * Take it **before** you read what you will follow. A write your read did not
5123
+ * see is then after the position and replayed; opening the feed afterwards
5124
+ * without a `since` starts after it and misses it.
5125
+ *
5126
+ * @example
5127
+ * ```typescript
5128
+ * const since = await relyio.changesHead()
5129
+ * const post = await relyio.content('post').findById(id).execute()
5130
+ * for await (const change of relyio.changes({ since, ids: [id] })) { … }
5131
+ * ```
5132
+ */
5133
+ changesHead(options = {}) {
5134
+ return readChangesHead(this.httpClient, options);
5135
+ }
4611
5136
  // ============================================================================
4612
5137
  // QUERY OPERATIONS
4613
5138
  // ============================================================================
@@ -4630,7 +5155,7 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
4630
5155
  * ```
4631
5156
  */
4632
5157
  adhoc() {
4633
- return this._adhocQuery.build();
5158
+ return this.query.build();
4634
5159
  }
4635
5160
  /**
4636
5161
  * Execute a predefined query by name (from schema)
@@ -4645,10 +5170,12 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
4645
5170
  * ```
4646
5171
  */
4647
5172
  async executeQuery(queryName, params) {
5173
+ const clientLocale = this.config.locale;
5174
+ const namesLocale = params?.locale != null || params?.locales != null;
4648
5175
  const response = await this.httpClient.request({
4649
5176
  method: "GET",
4650
5177
  url: `/${API_VERSION_SEGMENT}/${String(queryName)}`,
4651
- params: params ?? {}
5178
+ params: namesLocale || clientLocale === void 0 || clientLocale === "" ? params ?? {} : { ...params, locale: clientLocale }
4652
5179
  });
4653
5180
  const payload = readPayload(response.body);
4654
5181
  return Array.isArray(payload) ? payload : [payload];
@@ -4713,16 +5240,53 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
4713
5240
  }
4714
5241
  }
4715
5242
  /**
4716
- * Update the configuration and reinitialize all clients
5243
+ * Change some of the client's options and keep the rest.
5244
+ *
5245
+ * What you pass is laid over the configuration the client holds, field by field:
5246
+ *
5247
+ * - an option you name replaces the value the client holds;
5248
+ * - an option left out, or given as `undefined`, keeps the value the client holds;
5249
+ * - an option with no default given as `null` is cleared — `locale`, `dataset`, `apiKey`, `dev`,
5250
+ * `fetch`, `browser.onCorsError`, `realtime.wsUrl`, `debug.logger` — so the client runs as though
5251
+ * it had never been given one. An option with a default takes no `null`;
5252
+ * - a bag of options you name — `browser`, `cache`, `realtime`, `debug` — keeps every field of it
5253
+ * you do not name, so `updateConfig({ browser: { includeCredentials: true } })` keeps a
5254
+ * configured `onCorsError`;
5255
+ * - `headers` is laid over the headers the client sends, a name matched without regard to case: a
5256
+ * header you name is added or replaces every spelling of it, every other one is still sent, and a
5257
+ * header given as `null` is no longer sent;
5258
+ * - naming `apiKey` — a key, or `null` — replaces every key the client holds, one given in
5259
+ * `headers` included.
5260
+ *
5261
+ * The configuration it is laid over is what `createClient()`, `setApiKey()` and earlier updates
5262
+ * gave the client. The `relyio dev` handshake is read again, as `createClient()` reads it: while a
5263
+ * sandbox is active its key and dataset are sent over the configured ones, and once it has gone
5264
+ * the configured ones are.
5265
+ *
5266
+ * The client's one HTTP client is changed in place, so every request from now on is sent as the
5267
+ * update says — including one from a `content()`, `asset()`, `singleton()` or `settings()`
5268
+ * sub-client, a `query` client, or a builder such as `search()` or `batch()`, taken before the
5269
+ * call. The locale reaches them too: each reads the client's locale when it sends, so a new
5270
+ * `locale` is the default of its next request and `locale: null` leaves that request with none. A
5271
+ * locale the call names, and a sub-client's own `withLocale()`, still win over it. A client made
5272
+ * by `withLocale()` is a client of its own: an update of this one does not reach it.
5273
+ *
5274
+ * @example
5275
+ * ```typescript
5276
+ * const relyio = createClient({ apiKey, headers: { 'X-Tenant': 'acme' }, browser: { onCorsError } })
5277
+ *
5278
+ * relyio.updateConfig({ headers: { 'X-Trace': traceId } }) // X-Tenant is still sent
5279
+ * relyio.updateConfig({ browser: { includeCredentials: true } }) // onCorsError is kept
5280
+ * relyio.updateConfig({ headers: { 'X-Tenant': null } }) // X-Tenant is no longer sent
5281
+ * relyio.updateConfig({ locale: null }) // reads name no locale of the client's
5282
+ * ```
4717
5283
  */
4718
- updateConfig(newConfig) {
4719
- const resolved = resolveConfig({ ...this.config, ...newConfig });
5284
+ updateConfig(update) {
5285
+ const resolved = resolveConfigUpdate(this.configured, update);
4720
5286
  this.config = resolved.config;
5287
+ this.configured = resolved.configured;
4721
5288
  this._sandbox = resolved.sandbox;
4722
- this.httpClient = new HttpClient(this.config);
4723
- this._search = new SearchClient(this.httpClient);
4724
- this._batch = new BatchClient(this.httpClient);
4725
- this._adhocQuery = new AdhocQueryClient(this.httpClient);
5289
+ this.httpClient.configure(this.config);
4726
5290
  }
4727
5291
  /**
4728
5292
  * Get the current configuration
@@ -4731,11 +5295,31 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
4731
5295
  return { ...this.config };
4732
5296
  }
4733
5297
  /**
4734
- * Set the API key
5298
+ * Replace the API key every later request sends as `X-API-Key`.
5299
+ *
5300
+ * It reaches every request this client makes from now on, including those of a `content()`,
5301
+ * `asset()` or other sub-client taken before the call, and `getConfig().apiKey` reports it. A key
5302
+ * given in `headers`, under any spelling, is replaced with it and does not come back on a later
5303
+ * `updateConfig()`. A client already derived with `withLocale()` is a client of its own and keeps
5304
+ * its key.
5305
+ *
5306
+ * An empty or blank key is no key, as it is when passed to `createClient`: the next request
5307
+ * rejects with `MissingApiKeyError` and nothing is sent.
5308
+ *
5309
+ * Under an active `relyio dev` sandbox the sandbox's scoped key stands, as it does over the key
5310
+ * passed to `createClient` — replacing the configured key does not retarget the client at the
5311
+ * sandbox's dataset with a key the sandbox did not issue.
5312
+ *
5313
+ * @example
5314
+ * ```typescript
5315
+ * const relyio = createClient({ apiKey: process.env.RELYIO_API_KEY! })
5316
+ * relyio.setApiKey(rotatedKey) // the next read sends `X-API-Key: <rotatedKey>`
5317
+ * ```
4735
5318
  */
4736
5319
  setApiKey(apiKey) {
4737
- this.config.apiKey = apiKey;
4738
- this.httpClient.setAuthToken(apiKey);
5320
+ this.configured = withConfiguredKey(this.configured, apiKey);
5321
+ if (this._sandbox.state === "active") return;
5322
+ this.httpClient.setApiKey(apiKey);
4739
5323
  }
4740
5324
  /**
4741
5325
  * Get the HTTP client instance (for advanced usage)
@@ -4748,7 +5332,13 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
4748
5332
  // ============================================================================
4749
5333
  /**
4750
5334
  * Create a locale-scoped client
4751
- * All content operations on the returned client will use the specified locale
5335
+ *
5336
+ * Every call the returned client builds is in this locale unless the call names one of its own,
5337
+ * which wins: every read — `content()`, `asset()`, `singleton()`, `settings()`, `search()`,
5338
+ * `similar()`, `autocomplete()`, `executeQuery()` and the ad-hoc queries — and every write and
5339
+ * version-history read — `create()` and `update()`, the singleton and settings writes, the asset
5340
+ * file writes, `versions()` and `version()`, and each operation `batch()` builds. `restore()`
5341
+ * always names its locale; a delete, and a `batch().raw()` operation, carry none of the client's.
4752
5342
  *
4753
5343
  * Type-safe when schema is provided: autocompletes valid locale codes!
4754
5344
  *
@@ -4756,9 +5346,11 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
4756
5346
  * ```typescript
4757
5347
  * const spanish = relyio.withLocale('es-ES') // ← Autocompletes with schema!
4758
5348
  *
4759
- * // All operations use Spanish
5349
+ * // Reads and writes use Spanish
4760
5350
  * const products = await spanish.content('product').query().execute()
4761
5351
  * const homepage = await spanish.singleton('homepage').fetch()
5352
+ * const results = await spanish.search('zapatos').execute()
5353
+ * await spanish.content('product').update(id, { title: 'Zapatos' })
4762
5354
  *
4763
5355
  * // Original client unchanged
4764
5356
  * const englishProducts = await relyio.content('product').query().execute()
@@ -4766,7 +5358,7 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
4766
5358
  */
4767
5359
  withLocale(locale) {
4768
5360
  const newConfig = {
4769
- ...this.getConfig(),
5361
+ ...this.configured,
4770
5362
  locale,
4771
5363
  schema: this._schema
4772
5364
  };
@@ -4820,7 +5412,6 @@ exports.AdhocQueryClient = AdhocQueryClient;
4820
5412
  exports.AssetClient = AssetClient;
4821
5413
  exports.AssetFileBuilder = AssetFileBuilder;
4822
5414
  exports.AssetUploadBuilder = AssetUploadBuilder;
4823
- exports.AuthenticationError = AuthenticationError;
4824
5415
  exports.AuthorizationError = AuthorizationError;
4825
5416
  exports.AutocompleteBuilder = AutocompleteBuilder;
4826
5417
  exports.BaseValidationError = BaseValidationError;
@@ -4836,7 +5427,6 @@ exports.FileUploadError = FileUploadError;
4836
5427
  exports.FileValidationError = FileValidationError;
4837
5428
  exports.HttpClient = HttpClient;
4838
5429
  exports.InvalidApiKeyError = InvalidApiKeyError;
4839
- exports.InvalidFieldTypeError = InvalidFieldTypeError;
4840
5430
  exports.InvalidPayloadError = InvalidPayloadError;
4841
5431
  exports.MethodNotAllowedError = MethodNotAllowedError;
4842
5432
  exports.MissingApiKeyError = MissingApiKeyError;
@@ -4845,12 +5435,10 @@ exports.PayloadTooLargeError = PayloadTooLargeError;
4845
5435
  exports.PlanFeatureUnavailableError = PlanFeatureUnavailableError;
4846
5436
  exports.RateLimitError = RateLimitError;
4847
5437
  exports.ReadOnlyKeyError = ReadOnlyKeyError;
4848
- exports.RequiredFieldError = RequiredFieldError;
4849
5438
  exports.ResourceConflictError = ResourceConflictError;
4850
5439
  exports.ResourceExpiredError = ResourceExpiredError;
4851
5440
  exports.ResourceNotFoundError = ResourceNotFoundError;
4852
5441
  exports.SDKError = SDKError;
4853
- exports.SchemaValidationError = SchemaValidationError;
4854
5442
  exports.SearchBuilder = SearchBuilder;
4855
5443
  exports.SearchClient = SearchClient;
4856
5444
  exports.ServerError = ServerError;
@@ -4866,9 +5454,12 @@ exports.UsageQuotaExceededError = UsageQuotaExceededError;
4866
5454
  exports.ValidationError = ValidationError;
4867
5455
  exports.createClient = createClient;
4868
5456
  exports.entityTagOf = entityTagOf;
5457
+ exports.isAborted = isAborted;
4869
5458
  exports.isAssetEntry = isAssetEntry;
4870
5459
  exports.isResourceNotFound = isResourceNotFound;
4871
5460
  exports.matchRoute = matchRoute;
5461
+ exports.serviceInstructedDelayMs = serviceInstructedDelayMs;
5462
+ exports.sleep = sleep;
4872
5463
  exports.validateFile = validateFile;
4873
- //# sourceMappingURL=chunk-CIRT6TVT.cjs.map
4874
- //# sourceMappingURL=chunk-CIRT6TVT.cjs.map
5464
+ //# sourceMappingURL=chunk-VH2DE7HZ.cjs.map
5465
+ //# sourceMappingURL=chunk-VH2DE7HZ.cjs.map