@relyio/sdk 0.6.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 +370 -0
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/{chunk-DY2XT7OF.cjs → chunk-72UK5U7T.cjs} +70 -60
  4. package/dist/{chunk-7ABNXVWL.js → chunk-GYHAVYND.js} +777 -399
  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-GCMQUM6N.cjs → chunk-VH2DE7HZ.cjs} +776 -402
  8. package/dist/{chunk-XJVY2CLY.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-d1Kz1e19.d.cts → define-content-type-DiVpbwNs.d.cts} +1615 -684
  14. package/dist/{index-yPbhZc6q.d.ts → define-content-type-DiVpbwNs.d.ts} +1615 -684
  15. package/dist/{define-schema-1kr2HIzN.d.cts → define-schema-C-5an28C.d.ts} +189 -85
  16. package/dist/{define-schema-ChvAzY8H.d.ts → define-schema-D7MButVt.d.cts} +189 -85
  17. package/dist/{index-XqoGUaAY.d.ts → index-1G97sNA8.d.ts} +489 -267
  18. package/dist/index-BOQIHnsh.d.ts +818 -0
  19. package/dist/index-DbsTUw3Q.d.cts +818 -0
  20. package/dist/{index-Df1VsfJR.d.cts → index-M0X45k7J.d.cts} +489 -267
  21. package/dist/index.cjs +98 -114
  22. package/dist/index.d.cts +31 -31
  23. package/dist/index.d.ts +31 -31
  24. package/dist/index.js +8 -8
  25. package/dist/schema/index.cjs +35 -35
  26. package/dist/schema/index.d.cts +13 -11
  27. package/dist/schema/index.d.ts +13 -11
  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-CDB3kyeA.d.cts +0 -1173
  37. package/dist/define-content-type-CDB3kyeA.d.ts +0 -1173
@@ -1,4 +1,5 @@
1
1
  import { parseDevEnv, DEV_CONTEXT_DIR, DEV_CONTEXT_FILENAME, parseDevContextFile } from './chunk-BBUB7C2Z.js';
2
+ import { isBrowser } from './chunk-H6IBQSMM.js';
2
3
  import { __name, __publicField } from './chunk-XQ4QHWZE.js';
3
4
 
4
5
  /* RelyIO SDK - Built with ❤️ by the RelyIO Team */
@@ -230,18 +231,6 @@ __name(_UnknownContractError, "UnknownContractError");
230
231
  var UnknownContractError = _UnknownContractError;
231
232
 
232
233
  // src/errors/auth-errors.ts
233
- var _AuthenticationError = class _AuthenticationError extends SDKError {
234
- constructor(message = "Authentication failed", statusCode = 401, cause, context) {
235
- super(message, statusCode, cause, context);
236
- __publicField(this, "code", "AUTHENTICATION_ERROR");
237
- __publicField(this, "retryable", false);
238
- }
239
- getUserMessage() {
240
- return "Authentication failed. Please check your API key and try again.";
241
- }
242
- };
243
- __name(_AuthenticationError, "AuthenticationError");
244
- var AuthenticationError = _AuthenticationError;
245
234
  var _AuthorizationError = class _AuthorizationError extends SDKError {
246
235
  constructor(message = "Authorization failed", statusCode = 403, cause, context) {
247
236
  super(message, statusCode, cause, context);
@@ -420,58 +409,6 @@ var _InvalidPayloadError = class _InvalidPayloadError extends BaseValidationErro
420
409
  };
421
410
  __name(_InvalidPayloadError, "InvalidPayloadError");
422
411
  var InvalidPayloadError = _InvalidPayloadError;
423
- var _SchemaValidationError = class _SchemaValidationError extends BaseValidationError {
424
- constructor(message = "Schema validation failed", violations = [], cause) {
425
- super(message, violations, 400, cause);
426
- __publicField(this, "code", "SCHEMA_VALIDATION_ERROR");
427
- }
428
- getUserMessage() {
429
- return "The data does not match the expected schema. Please check your content structure.";
430
- }
431
- };
432
- __name(_SchemaValidationError, "SchemaValidationError");
433
- var SchemaValidationError = _SchemaValidationError;
434
- var _RequiredFieldError = class _RequiredFieldError extends BaseValidationError {
435
- constructor(fieldName, message, cause) {
436
- const violation = {
437
- field: fieldName,
438
- message: message ?? `Field "${fieldName}" is required`,
439
- code: "REQUIRED"
440
- };
441
- super(`Required field "${fieldName}" is missing`, [violation], 400, cause);
442
- __publicField(this, "code", "REQUIRED_FIELD_ERROR");
443
- }
444
- getUserMessage() {
445
- const violation = this.violations[0];
446
- const fieldName = violation?.field;
447
- return `The field "${fieldName}" is required and cannot be empty.`;
448
- }
449
- };
450
- __name(_RequiredFieldError, "RequiredFieldError");
451
- var RequiredFieldError = _RequiredFieldError;
452
- var _InvalidFieldTypeError = class _InvalidFieldTypeError extends BaseValidationError {
453
- constructor(fieldName, expectedType, actualType, value, cause) {
454
- const violation = {
455
- field: fieldName,
456
- message: `Expected ${expectedType}, got ${actualType}`,
457
- code: "INVALID_TYPE",
458
- value
459
- };
460
- super(
461
- `Invalid type for field "${fieldName}": expected ${expectedType}, got ${actualType}`,
462
- [violation],
463
- 400,
464
- cause
465
- );
466
- __publicField(this, "code", "INVALID_FIELD_TYPE");
467
- }
468
- getUserMessage() {
469
- const violation = this.violations[0];
470
- return `The field "${violation?.field}" has an invalid type. ${violation?.message}.`;
471
- }
472
- };
473
- __name(_InvalidFieldTypeError, "InvalidFieldTypeError");
474
- var InvalidFieldTypeError = _InvalidFieldTypeError;
475
412
 
476
413
  // ../../platform/api-contract/dist/index.js
477
414
  var HEADER_RETRY_AFTER = { name: "Retry-After" };
@@ -871,6 +808,20 @@ function isAborted(signal) {
871
808
  }
872
809
  __name(isAborted, "isAborted");
873
810
 
811
+ // src/http/request-timeout.ts
812
+ var MAX_TIMER_DELAY_MS = 2147483647;
813
+ function isTimerDelay(value) {
814
+ return typeof value === "number" && value >= 1 && value <= MAX_TIMER_DELAY_MS;
815
+ }
816
+ __name(isTimerDelay, "isTimerDelay");
817
+ function assertRequestTimeout(timeout) {
818
+ if (isTimerDelay(timeout)) return;
819
+ throw new Error(
820
+ `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)}`
821
+ );
822
+ }
823
+ __name(assertRequestTimeout, "assertRequestTimeout");
824
+
874
825
  // src/errors/instructed-delay.ts
875
826
  function serviceInstructedDelayMs(error, nowMs) {
876
827
  if (error instanceof ServiceIndeterminateError) {
@@ -924,43 +875,31 @@ var DefaultRetryPolicy = _DefaultRetryPolicy;
924
875
  // src/http/http-client.ts
925
876
  var _HttpClient = class _HttpClient {
926
877
  constructor(config) {
927
- __publicField(this, "baseUrl");
928
- __publicField(this, "defaultHeaders");
929
- __publicField(this, "timeout");
930
- __publicField(this, "retryPolicy");
931
- __publicField(this, "debug");
932
- __publicField(this, "logger");
933
- __publicField(this, "config");
934
- this.config = config;
935
- this.baseUrl = config.baseUrl;
936
- this.timeout = config.timeout;
937
- this.debug = config.debug.enabled ?? false;
938
- this.logger = config.debug.logger;
939
- const apiKey = presentKey(config.apiKey);
940
- this.defaultHeaders = {
941
- "Content-Type": "application/json",
942
- ...apiKey === void 0 ? {} : { [API_KEY_HEADER]: apiKey },
943
- ...config.headers
944
- };
945
- if (config.dataset != null) {
946
- this.defaultHeaders["X-RelyIO-Dataset"] = config.dataset;
947
- }
948
- if (typeof window === "undefined") {
949
- const globalWithVersion = globalThis;
950
- this.defaultHeaders["User-Agent"] = `RelyIO-SDK/${globalWithVersion.__VERSION__ ?? "0.0.0"}`;
951
- }
952
- this.retryPolicy = new DefaultRetryPolicy({
953
- maxAttempts: config.retryAttempts,
954
- baseDelay: 1e3,
955
- maxDelay: 1e4,
956
- backoffFactor: 2,
957
- jitter: true
958
- });
878
+ /** What every request reads off the configuration, derived by {@link configure} and nowhere else. */
879
+ __publicField(this, "connection");
880
+ this.connection = connectionFor(config);
881
+ this.announce();
882
+ }
883
+ /**
884
+ * Run on `config` from now on: every later request — whoever holds this client — is sent to its
885
+ * `baseUrl`, with its headers, key and dataset, its timeout and retries, through its `fetch`, and
886
+ * logged by its `debug` options.
887
+ *
888
+ * The client's own `updateConfig()` calls this rather than building a second client, so a
889
+ * `content()` or other sub-client taken before the update sends through the update too. Anything
890
+ * set on this client since it was last configured — `setDefaultHeaders()`, `setBaseUrl()` — is
891
+ * replaced by what `config` says.
892
+ */
893
+ configure(config) {
894
+ this.connection = connectionFor(config);
895
+ this.announce();
896
+ }
897
+ announce() {
959
898
  this.log("debug", "HttpClient initialized", {
960
- baseUrl: this.baseUrl,
961
- timeout: this.timeout,
962
- retryAttempts: config.retryAttempts,
963
- environment: typeof window === "undefined" ? "node" : "browser"
899
+ baseUrl: this.connection.baseUrl,
900
+ timeout: this.connection.timeout,
901
+ retryAttempts: this.connection.config.retryAttempts,
902
+ environment: isBrowser() ? "browser" : "node"
964
903
  });
965
904
  }
966
905
  /**
@@ -997,19 +936,24 @@ var _HttpClient = class _HttpClient {
997
936
  *
998
937
  * The loop always makes one attempt. The incumbent's `while (attempt <
999
938
  * maxAttempts)` made none at `retryAttempts: 0` — a value the config validator
1000
- * accepts — and then dereferenced an unassigned `lastError`.
939
+ * then accepted, and now refuses — and then dereferenced an unassigned
940
+ * `lastError`. `retryAttempts` is the most attempts, the first included; an
941
+ * `HttpClient` constructed directly is not validated, so the first attempt
942
+ * stays unconditional.
1001
943
  *
1002
944
  * A request that would carry no `X-API-Key` makes none: it rejects with
1003
945
  * `MissingApiKeyError` before `fetch` is called, is not retried, and names
1004
946
  * `apiKey`. This is where a client built without a key learns it — the
1005
947
  * constructor accepts one so that a module-scope client can be evaluated by a
1006
- * build that holds no key (REL-1120).
948
+ * build that holds no key (REL-1120) — and so does one whose `setApiKey` was
949
+ * given none.
1007
950
  */
1008
951
  async request(config) {
952
+ if (config.timeout !== void 0) assertRequestTimeout(config.timeout);
1009
953
  const mergedConfig = this.mergeRequestConfig(config);
1010
954
  if (presentKey(headerValue(mergedConfig.headers, API_KEY_HEADER)) === void 0) {
1011
955
  throw new MissingApiKeyError(
1012
- "`apiKey` is missing: this client was created without an API key, so the request was not sent. Pass `apiKey` to createClient \u2014 the documented setup reads it from RELYIO_API_KEY.",
956
+ "`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.",
1013
957
  { url: mergedConfig.url, method: mergedConfig.method, sent: false }
1014
958
  );
1015
959
  }
@@ -1019,7 +963,7 @@ var _HttpClient = class _HttpClient {
1019
963
  // Show the final URL with query params
1020
964
  });
1021
965
  if (config.method === "DELETE" && mergedConfig.headers) {
1022
- delete mergedConfig.headers["Content-Type"];
966
+ mergedConfig.headers = layerHeaders(mergedConfig.headers, { "Content-Type": null });
1023
967
  }
1024
968
  for (let attempt = 1; ; attempt++) {
1025
969
  try {
@@ -1038,8 +982,8 @@ var _HttpClient = class _HttpClient {
1038
982
  attempt,
1039
983
  url: mergedConfig.url
1040
984
  });
1041
- if (this.retryPolicy.shouldRetry(attempt, failure, mergedConfig)) {
1042
- const delay = this.retryPolicy.delayFor(attempt, failure);
985
+ if (this.connection.retryPolicy.shouldRetry(attempt, failure, mergedConfig)) {
986
+ const delay = this.connection.retryPolicy.delayFor(attempt, failure);
1043
987
  this.log("debug", "Retrying request", { attempt, delay });
1044
988
  await sleep(delay, config.signal);
1045
989
  continue;
@@ -1111,34 +1055,60 @@ var _HttpClient = class _HttpClient {
1111
1055
  });
1112
1056
  }
1113
1057
  /**
1114
- * Set default headers for all requests
1058
+ * Add headers to every later request, each replacing any header of the same name — the name
1059
+ * matched without regard to case — until the client is next configured.
1115
1060
  */
1116
1061
  setDefaultHeaders(headers) {
1117
- this.defaultHeaders = { ...this.defaultHeaders, ...headers };
1062
+ this.connection = {
1063
+ ...this.connection,
1064
+ defaultHeaders: layerHeaders(this.connection.defaultHeaders, headers)
1065
+ };
1118
1066
  }
1119
1067
  /**
1120
- * Set authentication token
1068
+ * Replace the key every later request sends as `X-API-Key`, and the `apiKey` the configuration
1069
+ * reports.
1070
+ *
1071
+ * The key is read the way construction reads it (`presentKey`): an absent, empty or blank key is
1072
+ * no key, so the header is dropped and the next request is refused with `MissingApiKeyError`
1073
+ * before anything is sent. A key header of any other spelling — one given in `headers` — goes
1074
+ * too, from the headers sent and from the headers the configuration reports, so the old key is
1075
+ * never sent beside the new one.
1076
+ *
1077
+ * The configuration is the one the owning `TypedRelyIOClient` holds (it hands its own to the
1078
+ * constructor), so its `getConfig().apiKey` — what a surface reads to decide a key's kind — is the
1079
+ * key sent from here on.
1080
+ *
1081
+ * This replaced `setAuthToken`, which added `Authorization: Bearer` beside an `X-API-Key` it
1082
+ * never touched: the API reads `X-API-Key` first, so the old key was the one used (REL-1385).
1121
1083
  */
1122
- setAuthToken(token) {
1123
- this.defaultHeaders.Authorization = `Bearer ${token}`;
1084
+ setApiKey(apiKey) {
1085
+ const { config } = this.connection;
1086
+ config.apiKey = apiKey;
1087
+ config.headers = layerHeaders(config.headers, { [API_KEY_HEADER]: null });
1088
+ this.connection = {
1089
+ ...this.connection,
1090
+ defaultHeaders: layerHeaders(this.connection.defaultHeaders, {
1091
+ [API_KEY_HEADER]: presentKey(apiKey) ?? null
1092
+ })
1093
+ };
1124
1094
  }
1125
1095
  /**
1126
1096
  * Get current default headers
1127
1097
  */
1128
1098
  getDefaultHeaders() {
1129
- return { ...this.defaultHeaders };
1099
+ return { ...this.connection.defaultHeaders };
1130
1100
  }
1131
1101
  /**
1132
1102
  * Update base URL
1133
1103
  */
1134
1104
  setBaseUrl(baseUrl) {
1135
- this.baseUrl = baseUrl;
1105
+ this.connection = { ...this.connection, baseUrl };
1136
1106
  }
1137
1107
  /**
1138
1108
  * Get current base URL
1139
1109
  */
1140
1110
  getBaseUrl() {
1141
- return this.baseUrl;
1111
+ return this.connection.baseUrl;
1142
1112
  }
1143
1113
  /**
1144
1114
  * One attempt.
@@ -1160,7 +1130,7 @@ var _HttpClient = class _HttpClient {
1160
1130
  const timeoutId = setTimeout(() => {
1161
1131
  timedOut = true;
1162
1132
  controller.abort();
1163
- }, config.timeout ?? this.timeout);
1133
+ }, config.timeout ?? this.connection.timeout);
1164
1134
  const onCallerAbort = /* @__PURE__ */ __name(() => controller.abort(), "onCallerAbort");
1165
1135
  caller?.addEventListener("abort", onCallerAbort, { once: true });
1166
1136
  try {
@@ -1170,14 +1140,15 @@ var _HttpClient = class _HttpClient {
1170
1140
  headers: filteredHeaders,
1171
1141
  body: this.prepareRequestBody(config),
1172
1142
  signal: controller.signal,
1173
- // Only where `window` exists. A server or edge runtime has no cookie jar for the mode to
1174
- // govern, so the field is left off and the runtime's default stands. A Web Worker gets
1175
- // that default too: it has cookies but no `window`.
1176
- ...typeof window !== "undefined" && {
1177
- credentials: credentialsModeFor(this.config.browser.includeCredentials)
1143
+ // Only in a browser — a `window` with a `document` (`isBrowser()`). A server or edge
1144
+ // runtime has no cookie jar for the mode to govern, so the field is left off and the
1145
+ // runtime's default stands; that includes Deno 1.x, whose global is a `window` with no
1146
+ // `document`. A Web Worker gets that default too: it has cookies but no `window`.
1147
+ ...isBrowser() && {
1148
+ credentials: credentialsModeFor(this.connection.config.browser.includeCredentials)
1178
1149
  }
1179
1150
  };
1180
- const send = this.config.fetch ?? globalThis.fetch;
1151
+ const send = this.connection.config.fetch ?? globalThis.fetch;
1181
1152
  const response = await send(url, requestInit);
1182
1153
  clearTimeout(timeoutId);
1183
1154
  const responseBody = await this.parseResponse(response);
@@ -1210,10 +1181,8 @@ var _HttpClient = class _HttpClient {
1210
1181
  }
1211
1182
  if (error instanceof DOMException && error.name === "AbortError") {
1212
1183
  if (!timedOut && isAborted(caller)) throw error;
1213
- throw new TimeoutError(
1214
- `Request timed out after ${config.timeout ?? this.timeout}ms`,
1215
- config.timeout ?? this.timeout
1216
- );
1184
+ const timeout = config.timeout ?? this.connection.timeout;
1185
+ throw new TimeoutError(`Request timed out after ${timeout}ms`, timeout);
1217
1186
  }
1218
1187
  if (error instanceof TypeError && error.message.includes("CORS")) {
1219
1188
  const corsError = new NetworkError(
@@ -1227,9 +1196,10 @@ var _HttpClient = class _HttpClient {
1227
1196
  suggestion: "Check if the API server allows requests from your domain and has proper CORS headers configured."
1228
1197
  }
1229
1198
  );
1230
- if (typeof window !== "undefined" && this.config.browser.onCorsError) {
1199
+ const { onCorsError } = this.connection.config.browser;
1200
+ if (isBrowser() && onCorsError) {
1231
1201
  try {
1232
- this.config.browser.onCorsError(corsError, {
1202
+ onCorsError(corsError, {
1233
1203
  url: config.url,
1234
1204
  method: config.method,
1235
1205
  originalError: error
@@ -1246,20 +1216,20 @@ var _HttpClient = class _HttpClient {
1246
1216
  }
1247
1217
  }
1248
1218
  /**
1249
- * Filter headers that browsers don't allow to be set manually
1219
+ * The headers without those a browser controls itself, when this is a browser; unchanged when it
1220
+ * is not.
1221
+ *
1222
+ * A name is matched without regard to case (RFC 9110 §5.1), because a browser refuses `Origin`
1223
+ * whatever case a page spells it in. This matched only the all-lowercase and all-uppercase
1224
+ * spellings, so `Origin` and `User-Agent` — how people write them — reached `fetch` (REL-1409).
1250
1225
  */
1251
1226
  filterHeadersForBrowser(headers) {
1252
- if (typeof window !== "undefined") {
1253
- const filteredHeaders = { ...headers };
1254
- const forbiddenHeaders = ["user-agent", "referer", "origin", "host", "connection", "upgrade"];
1255
- forbiddenHeaders.forEach((header) => {
1256
- delete filteredHeaders[header];
1257
- delete filteredHeaders[header.toLowerCase()];
1258
- delete filteredHeaders[header.toUpperCase()];
1259
- });
1260
- return filteredHeaders;
1261
- }
1262
- return headers;
1227
+ if (!isBrowser()) return headers;
1228
+ return Object.fromEntries(
1229
+ Object.entries(headers).filter(
1230
+ ([name]) => !BROWSER_CONTROLLED_HEADERS.has(name.toLowerCase())
1231
+ )
1232
+ );
1263
1233
  }
1264
1234
  /**
1265
1235
  * Prepare request body based on content type and environment
@@ -1295,7 +1265,7 @@ var _HttpClient = class _HttpClient {
1295
1265
  if (url.startsWith("http://") || url.startsWith("https://")) {
1296
1266
  return url;
1297
1267
  }
1298
- return `${this.baseUrl}${url.startsWith("/") ? "" : "/"}${url}`;
1268
+ return `${this.connection.baseUrl}${url.startsWith("/") ? "" : "/"}${url}`;
1299
1269
  }
1300
1270
  mergeRequestConfig(config) {
1301
1271
  const url = this.resolveUrl(config.url);
@@ -1304,11 +1274,11 @@ var _HttpClient = class _HttpClient {
1304
1274
  return {
1305
1275
  ...config,
1306
1276
  url: finalUrl,
1307
- headers: {
1308
- ...this.defaultHeaders,
1309
- ...config.headers
1310
- },
1311
- timeout: config.timeout ?? this.timeout
1277
+ // The request's own headers replace the client's of the same name, whatever the case: a
1278
+ // per-request `x-relyio-dataset` sent beside the client's `X-RelyIO-Dataset` is one header
1279
+ // `Headers` joins into `main, staging`.
1280
+ headers: layerHeaders(this.connection.defaultHeaders, config.headers),
1281
+ timeout: config.timeout ?? this.connection.timeout
1312
1282
  };
1313
1283
  }
1314
1284
  buildQueryString(params) {
@@ -1332,9 +1302,10 @@ var _HttpClient = class _HttpClient {
1332
1302
  return obj;
1333
1303
  }
1334
1304
  log(level, message, data) {
1335
- if (!this.debug) return;
1336
- if (this.logger != null) {
1337
- this.logger(level, message, data);
1305
+ const { debug } = this.connection.config;
1306
+ if (!debug.enabled) return;
1307
+ if (debug.logger != null) {
1308
+ debug.logger(level, message, data);
1338
1309
  } else {
1339
1310
  console.log(`[RelyIO SDK ${level.toUpperCase()}] ${message}`, data ?? "");
1340
1311
  }
@@ -1343,29 +1314,104 @@ var _HttpClient = class _HttpClient {
1343
1314
  __name(_HttpClient, "HttpClient");
1344
1315
  var HttpClient = _HttpClient;
1345
1316
  var API_KEY_HEADER = "X-API-Key";
1317
+ var DATASET_HEADER = "X-RelyIO-Dataset";
1318
+ var BROWSER_CONTROLLED_HEADERS = /* @__PURE__ */ new Set([
1319
+ "user-agent",
1320
+ "referer",
1321
+ "origin",
1322
+ "host",
1323
+ "connection",
1324
+ "upgrade"
1325
+ ]);
1326
+ function connectionFor(config) {
1327
+ const globalWithVersion = globalThis;
1328
+ const defaultHeaders = layerHeaders(
1329
+ layerHeaders({ "Content-Type": "application/json" }, config.headers),
1330
+ {
1331
+ [API_KEY_HEADER]: presentKey(config.apiKey),
1332
+ [DATASET_HEADER]: config.dataset,
1333
+ // Only outside a browser, which controls this header. "A browser" is the core's one
1334
+ // definition, `isBrowser()` — every environment question in this file asks it.
1335
+ "User-Agent": isBrowser() ? void 0 : `RelyIO-SDK/${globalWithVersion.__VERSION__ ?? "0.0.0"}`
1336
+ }
1337
+ );
1338
+ return {
1339
+ config,
1340
+ baseUrl: config.baseUrl,
1341
+ defaultHeaders,
1342
+ timeout: config.timeout,
1343
+ // The policy, not a predicate over `(error, attempt)`: whether a request may be sent again is a
1344
+ // property of the **route** as much as of the refusal, and a callback that never sees the
1345
+ // request cannot express that.
1346
+ retryPolicy: new DefaultRetryPolicy({
1347
+ maxAttempts: config.retryAttempts,
1348
+ baseDelay: 1e3,
1349
+ maxDelay: 1e4,
1350
+ backoffFactor: 2,
1351
+ jitter: true
1352
+ })
1353
+ };
1354
+ }
1355
+ __name(connectionFor, "connectionFor");
1356
+ function layerHeaders(base, over) {
1357
+ const layered = { ...base };
1358
+ for (const [name, value] of Object.entries(over ?? {})) {
1359
+ if (value === void 0) continue;
1360
+ const lower = name.toLowerCase();
1361
+ for (const existing of Object.keys(layered)) {
1362
+ if (existing.toLowerCase() === lower) delete layered[existing];
1363
+ }
1364
+ if (value !== null) layered[name] = value;
1365
+ }
1366
+ return layered;
1367
+ }
1368
+ __name(layerHeaders, "layerHeaders");
1346
1369
  function presentKey(value) {
1347
1370
  return typeof value === "string" && value.trim() !== "" ? value : void 0;
1348
1371
  }
1349
1372
  __name(presentKey, "presentKey");
1350
1373
  function credentialsModeFor(includeCredentials) {
1351
- switch (includeCredentials) {
1352
- case false: {
1353
- return "same-origin";
1354
- }
1355
- case true:
1356
- case void 0: {
1357
- return "include";
1358
- }
1359
- }
1374
+ return includeCredentials ? "include" : "same-origin";
1360
1375
  }
1361
1376
  __name(credentialsModeFor, "credentialsModeFor");
1362
1377
 
1378
+ // src/content/client-locale.ts
1379
+ function callLocale(named, clientLocale) {
1380
+ if (named !== void 0) return named;
1381
+ return clientLocale === void 0 || clientLocale === "" ? void 0 : clientLocale;
1382
+ }
1383
+ __name(callLocale, "callLocale");
1384
+ function readClientLocale(locale) {
1385
+ return typeof locale === "function" ? locale : () => locale;
1386
+ }
1387
+ __name(readClientLocale, "readClientLocale");
1388
+
1363
1389
  // src/content/query-builder.ts
1390
+ function namesMetadata(key) {
1391
+ return key === "_metadata" || key.startsWith("_metadata.");
1392
+ }
1393
+ __name(namesMetadata, "namesMetadata");
1394
+ function refuseMetadataConditions(conditions) {
1395
+ const named = Object.keys(conditions).filter(namesMetadata);
1396
+ if (named.length === 0) return;
1397
+ throw new ValidationError(
1398
+ `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.`,
1399
+ named.map((field) => ({
1400
+ field,
1401
+ code: "METADATA_CONDITION",
1402
+ message: `${field} is not a field the API can filter on`
1403
+ }))
1404
+ );
1405
+ }
1406
+ __name(refuseMetadataConditions, "refuseMetadataConditions");
1364
1407
  var _ContentQueryBuilder = class _ContentQueryBuilder {
1365
- constructor(httpClient, contentType, defaultLocale) {
1408
+ /**
1409
+ * @param locale - The client's locale (`withLocale()`, `updateConfig({ locale })`), sent when the
1410
+ * query names none: the function `TypedRelyIOClient` hands over, or a locale fixed for it
1411
+ */
1412
+ constructor(httpClient, contentType, locale) {
1366
1413
  this.httpClient = httpClient;
1367
1414
  this.contentType = contentType;
1368
- this.defaultLocale = defaultLocale;
1369
1415
  __publicField(this, "queryOptions", {});
1370
1416
  __publicField(this, "localeValue");
1371
1417
  __publicField(this, "localesArray");
@@ -1375,6 +1421,9 @@ var _ContentQueryBuilder = class _ContentQueryBuilder {
1375
1421
  __publicField(this, "pageNumber");
1376
1422
  __publicField(this, "pageSizeValue");
1377
1423
  __publicField(this, "requestHeaders");
1424
+ /** The client's locale, asked when the query is sent rather than copied when it was built. */
1425
+ __publicField(this, "clientLocale");
1426
+ this.clientLocale = readClientLocale(locale);
1378
1427
  }
1379
1428
  /**
1380
1429
  * Headers this query adds to the client's own, e.g. `X-RelyIO-Dataset` to list another dataset
@@ -1386,9 +1435,11 @@ var _ContentQueryBuilder = class _ContentQueryBuilder {
1386
1435
  return this;
1387
1436
  }
1388
1437
  /**
1389
- * Add where conditions to the query
1438
+ * Add where conditions to the query: conditions on the fields the content type declares. A
1439
+ * condition on `_metadata` is refused here, before anything is sent (`refuseMetadataConditions`).
1390
1440
  */
1391
1441
  where(conditions) {
1442
+ refuseMetadataConditions(conditions);
1392
1443
  this.queryOptions.where = {
1393
1444
  ...this.queryOptions.where,
1394
1445
  ...conditions
@@ -1645,7 +1696,11 @@ var _ContentQueryBuilder = class _ContentQueryBuilder {
1645
1696
  * Clone the query builder
1646
1697
  */
1647
1698
  clone() {
1648
- const cloned = new _ContentQueryBuilder(this.httpClient, this.contentType, this.defaultLocale);
1699
+ const cloned = new _ContentQueryBuilder(
1700
+ this.httpClient,
1701
+ this.contentType,
1702
+ this.clientLocale
1703
+ );
1649
1704
  cloned.queryOptions = JSON.parse(JSON.stringify(this.queryOptions));
1650
1705
  cloned.localeValue = this.localeValue;
1651
1706
  cloned.localesArray = this.localesArray ? [...this.localesArray] : void 0;
@@ -1720,14 +1775,15 @@ var _ContentQueryBuilder = class _ContentQueryBuilder {
1720
1775
  if (this.queryOptions.cursor !== void 0) {
1721
1776
  params.cursor = this.queryOptions.cursor;
1722
1777
  }
1778
+ const clientLocale = this.clientLocale();
1723
1779
  if (this.localeValue != null && this.localeValue !== "") {
1724
1780
  params.locale = this.localeValue;
1725
1781
  } else if (this.fetchAllLocales) {
1726
1782
  params.locale = "*";
1727
1783
  } else if (this.localesArray && this.localesArray.length > 0) {
1728
1784
  params.locales = this.localesArray.join(",");
1729
- } else if (this.defaultLocale != null && this.defaultLocale !== "") {
1730
- params.locale = this.defaultLocale;
1785
+ } else if (clientLocale != null && clientLocale !== "") {
1786
+ params.locale = clientLocale;
1731
1787
  }
1732
1788
  if (this.pageNumber !== void 0) {
1733
1789
  params.page = this.pageNumber;
@@ -1751,17 +1807,23 @@ var _ContentQueryBuilder = class _ContentQueryBuilder {
1751
1807
  __name(_ContentQueryBuilder, "ContentQueryBuilder");
1752
1808
  var ContentQueryBuilder = _ContentQueryBuilder;
1753
1809
  var _ContentByIdBuilder = class _ContentByIdBuilder {
1754
- constructor(httpClient, contentType, id, defaultLocale) {
1810
+ /**
1811
+ * @param locale - The client's locale (`withLocale()`, `updateConfig({ locale })`), sent when the
1812
+ * read names none: the function `TypedRelyIOClient` hands over, or a locale fixed for it
1813
+ */
1814
+ constructor(httpClient, contentType, id, locale) {
1755
1815
  this.httpClient = httpClient;
1756
1816
  this.contentType = contentType;
1757
1817
  this.id = id;
1758
- this.defaultLocale = defaultLocale;
1759
1818
  __publicField(this, "includeRelations");
1760
1819
  __publicField(this, "localeValue");
1761
1820
  __publicField(this, "localesArray");
1762
1821
  __publicField(this, "fetchAllLocales", false);
1763
1822
  __publicField(this, "childrenOptions");
1764
1823
  __publicField(this, "requestHeaders");
1824
+ /** The client's locale, asked when the read is sent rather than copied when it was built. */
1825
+ __publicField(this, "clientLocale");
1826
+ this.clientLocale = readClientLocale(locale);
1765
1827
  }
1766
1828
  /**
1767
1829
  * Include related content in the response
@@ -1831,14 +1893,15 @@ var _ContentByIdBuilder = class _ContentByIdBuilder {
1831
1893
  if (this.includeRelations != null) {
1832
1894
  params.include = this.includeRelations.join(",");
1833
1895
  }
1896
+ const clientLocale = this.clientLocale();
1834
1897
  if (this.localeValue != null && this.localeValue !== "") {
1835
1898
  params.locale = this.localeValue;
1836
1899
  } else if (this.fetchAllLocales) {
1837
1900
  params.locale = "*";
1838
1901
  } else if (this.localesArray && this.localesArray.length > 0) {
1839
1902
  params.locales = this.localesArray.join(",");
1840
- } else if (this.defaultLocale != null && this.defaultLocale !== "") {
1841
- params.locale = this.defaultLocale;
1903
+ } else if (clientLocale != null && clientLocale !== "") {
1904
+ params.locale = clientLocale;
1842
1905
  }
1843
1906
  if (this.childrenOptions != null) {
1844
1907
  if (this.childrenOptions.contentTypes) {
@@ -1903,22 +1966,28 @@ __name(entityTagOf, "entityTagOf");
1903
1966
 
1904
1967
  // src/content/content-client.ts
1905
1968
  var _ContentClient = class _ContentClient {
1906
- constructor(httpClient, contentType, defaultLocale) {
1969
+ /**
1970
+ * @param locale - The client's locale: the function `TypedRelyIOClient` hands over, asked on
1971
+ * every call, or a locale fixed for this client
1972
+ */
1973
+ constructor(httpClient, contentType, locale) {
1907
1974
  this.httpClient = httpClient;
1908
1975
  this.contentType = contentType;
1909
- this.defaultLocale = defaultLocale;
1976
+ /** The client's locale, asked on every call rather than copied when this client was taken. */
1977
+ __publicField(this, "clientLocale");
1978
+ this.clientLocale = readClientLocale(locale);
1910
1979
  }
1911
1980
  /**
1912
1981
  * Create a new query builder for this content type
1913
1982
  */
1914
1983
  query() {
1915
- return new ContentQueryBuilder(this.httpClient, this.contentType, this.defaultLocale);
1984
+ return new ContentQueryBuilder(this.httpClient, this.contentType, this.clientLocale);
1916
1985
  }
1917
1986
  /**
1918
1987
  * Find content by ID with fluent options
1919
1988
  */
1920
1989
  findById(id) {
1921
- return new ContentByIdBuilder(this.httpClient, this.contentType, id, this.defaultLocale);
1990
+ return new ContentByIdBuilder(this.httpClient, this.contentType, id, this.clientLocale);
1922
1991
  }
1923
1992
  /**
1924
1993
  * Fetch all content items
@@ -2002,7 +2071,7 @@ var _ContentClient = class _ContentClient {
2002
2071
  async fetchOneWithTag(id, options) {
2003
2072
  const query = {};
2004
2073
  if (options?.include != null) query.include = options.include.join(",");
2005
- const locale = options?.locale ?? this.defaultLocale;
2074
+ const locale = options?.locale ?? this.clientLocale();
2006
2075
  if (locale != null && locale !== "") query.locale = locale;
2007
2076
  const response = await this.httpClient.send(
2008
2077
  "/v1/{contentType}/{id}",
@@ -2032,16 +2101,17 @@ var _ContentClient = class _ContentClient {
2032
2101
  * @param options - Create options (slug, locale, parentId, sortOrder), and `headers` this one
2033
2102
  * request adds — e.g. `{ 'X-RelyIO-Dataset': 'staging' }` to write a dataset other than the
2034
2103
  * client's. They are sent, never put in the body; `idempotencyKey` still wins over an
2035
- * `Idempotency-Key` among them.
2104
+ * `Idempotency-Key` among them. `locale` defaults to the client's own (`withLocale()`).
2036
2105
  */
2037
2106
  async create(data, options) {
2038
- const { idempotencyKey, headers, ...body } = options ?? {};
2107
+ const { idempotencyKey, headers, locale, ...body } = options ?? {};
2108
+ const sent = callLocale(locale, this.clientLocale());
2039
2109
  return this.httpClient.fetchPayload(
2040
2110
  "/v1/{contentType}",
2041
2111
  "post",
2042
2112
  {
2043
2113
  path: { contentType: this.contentType },
2044
- body: { content: data, ...body },
2114
+ body: { content: data, ...body, ...sent === void 0 ? {} : { locale: sent } },
2045
2115
  headers: requestHeaders(headers, writeHeaders({ idempotencyKey }))
2046
2116
  }
2047
2117
  );
@@ -2052,16 +2122,18 @@ var _ContentClient = class _ContentClient {
2052
2122
  * @param data - Fields to update
2053
2123
  * @param options - Update options (slug, locale, parentId, sortOrder), and `headers` this one
2054
2124
  * request adds, as for {@link create}. `ifMatch` and `idempotencyKey` win over the same
2055
- * header among them.
2125
+ * header among them. `locale` defaults to the client's own, the locale whose entity tag
2126
+ * {@link fetchOneWithTag} reads, so the tag it hands back is checked against the same locale.
2056
2127
  */
2057
2128
  async update(id, data, options) {
2058
- const { idempotencyKey, ifMatch, headers, ...body } = options ?? {};
2129
+ const { idempotencyKey, ifMatch, headers, locale, ...body } = options ?? {};
2130
+ const sent = callLocale(locale, this.clientLocale());
2059
2131
  return this.httpClient.fetchPayload(
2060
2132
  "/v1/{contentType}/{id}",
2061
2133
  "patch",
2062
2134
  {
2063
2135
  path: { contentType: this.contentType, id },
2064
- body: { content: data, ...body },
2136
+ body: { content: data, ...body, ...sent === void 0 ? {} : { locale: sent } },
2065
2137
  headers: requestHeaders(headers, writeHeaders({ idempotencyKey, ifMatch }))
2066
2138
  }
2067
2139
  );
@@ -2093,32 +2165,35 @@ var _ContentClient = class _ContentClient {
2093
2165
  * Get version history for one locale of an entry.
2094
2166
  *
2095
2167
  * Versions are numbered per locale, so this is one locale's timeline: `locale` defaults to the
2096
- * schema default, no fallback is applied, and a locale with no history returns an empty array
2097
- * rather than another locale's rows. Every row names the locale it belongs to.
2168
+ * client's own (`withLocale()`), else the schema default; no fallback is applied, and a locale
2169
+ * with no history returns an empty array rather than another locale's rows. Every row names the
2170
+ * locale it belongs to.
2098
2171
  */
2099
2172
  async versions(id, options) {
2173
+ const locale = callLocale(options?.locale, this.clientLocale());
2100
2174
  return this.httpClient.fetchPayload("/v1/{contentType}/{id}/versions", "get", {
2101
2175
  path: { contentType: this.contentType, id },
2102
2176
  query: {
2103
2177
  limit: options?.limit ?? 10,
2104
- ...options?.locale === void 0 ? {} : { locale: options.locale }
2178
+ ...locale === void 0 ? {} : { locale }
2105
2179
  }
2106
2180
  });
2107
2181
  }
2108
2182
  /**
2109
2183
  * Get a specific version of an entry.
2110
2184
  *
2111
- * `locale` defaults to the schema default. Version numbers are per locale, so version 2 is
2112
- * different content in each one.
2185
+ * `locale` defaults to the client's own (`withLocale()`), else the schema default. Version
2186
+ * numbers are per locale, so version 2 is different content in each one.
2113
2187
  */
2114
2188
  async version(id, versionNumber, options) {
2189
+ const locale = callLocale(options?.locale, this.clientLocale());
2115
2190
  return this.httpClient.fetchPayload("/v1/{contentType}/{id}/versions/{versionNumber}", "get", {
2116
2191
  path: {
2117
2192
  contentType: this.contentType,
2118
2193
  id,
2119
2194
  versionNumber: String(versionNumber)
2120
2195
  },
2121
- query: options?.locale === void 0 ? void 0 : { locale: options.locale }
2196
+ query: locale === void 0 ? void 0 : { locale }
2122
2197
  });
2123
2198
  }
2124
2199
  /**
@@ -2157,7 +2232,7 @@ var _ContentClient = class _ContentClient {
2157
2232
  if (options?.include != null) {
2158
2233
  params.include = options.include.join(",");
2159
2234
  }
2160
- const locale = options?.locale ?? this.defaultLocale;
2235
+ const locale = options?.locale ?? this.clientLocale();
2161
2236
  if (locale != null && locale !== "") {
2162
2237
  params.locale = locale;
2163
2238
  }
@@ -2177,19 +2252,20 @@ var _ContentClient = class _ContentClient {
2177
2252
  * Create a new ContentClient for a different content type
2178
2253
  */
2179
2254
  ofType(contentType) {
2180
- return new _ContentClient(this.httpClient, contentType, this.defaultLocale);
2255
+ return new _ContentClient(this.httpClient, contentType, this.clientLocale);
2181
2256
  }
2182
2257
  /**
2183
- * Create a new ContentClient with a different default locale
2258
+ * Create a new ContentClient with a different default locale, fixed: an update of the client's
2259
+ * locale (`updateConfig({ locale })`) does not reach it
2184
2260
  */
2185
2261
  withLocale(locale) {
2186
2262
  return new _ContentClient(this.httpClient, this.contentType, locale);
2187
2263
  }
2188
2264
  /**
2189
- * Get the current default locale
2265
+ * Get the default locale as it is now: the client's, or the one `withLocale()` fixed
2190
2266
  */
2191
2267
  getDefaultLocale() {
2192
- return this.defaultLocale;
2268
+ return this.clientLocale();
2193
2269
  }
2194
2270
  };
2195
2271
  __name(_ContentClient, "ContentClient");
@@ -2198,7 +2274,7 @@ var ContentClient = _ContentClient;
2198
2274
  // src/errors/file-errors.ts
2199
2275
  var _FileUploadError = class _FileUploadError extends SDKError {
2200
2276
  constructor(code, message, options = {}) {
2201
- super(message, options.statusCode, options.cause, { fileErrorCode: code });
2277
+ super(message, options.statusCode, options.cause, { ...options.context, fileErrorCode: code });
2202
2278
  __publicField(this, "code");
2203
2279
  __publicField(this, "retryable");
2204
2280
  this.code = code;
@@ -2245,11 +2321,52 @@ function validateFile(file, constraints = {}) {
2245
2321
  }
2246
2322
  __name(validateFile, "validateFile");
2247
2323
 
2324
+ // src/errors/file-error-codes.ts
2325
+ var FILE_ERROR_CODES = [
2326
+ "UPLOAD_TOO_LARGE",
2327
+ "UNSUPPORTED_MIME",
2328
+ "DIMENSION_OUT_OF_RANGE",
2329
+ "ASPECT_RATIO",
2330
+ "CORRUPT_FILE",
2331
+ "UNSUPPORTED_CODEC",
2332
+ "IMAGES_LIMIT_EXCEEDED",
2333
+ "STREAM_ENCODE_FAILED",
2334
+ "SCAN_INFECTED",
2335
+ "INTERNAL"
2336
+ ];
2337
+
2248
2338
  // src/content/asset-client.ts
2339
+ function assertWaitOptions(options) {
2340
+ const { timeoutMs, pollIntervalMs } = options;
2341
+ if (timeoutMs !== void 0 && !(typeof timeoutMs === "number" && timeoutMs >= 0 && timeoutMs <= MAX_TIMER_DELAY_MS)) {
2342
+ throw new Error(
2343
+ `timeoutMs must be a number of milliseconds from 0 to ${MAX_TIMER_DELAY_MS}, and got ${String(timeoutMs)}`
2344
+ );
2345
+ }
2346
+ if (pollIntervalMs !== void 0 && !isTimerDelay(pollIntervalMs)) {
2347
+ throw new Error(
2348
+ `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)}`
2349
+ );
2350
+ }
2351
+ }
2352
+ __name(assertWaitOptions, "assertWaitOptions");
2249
2353
  function hasText(value) {
2250
2354
  return value != null && value !== "";
2251
2355
  }
2252
2356
  __name(hasText, "hasText");
2357
+ function processingFailure(subject, stored) {
2358
+ const storedCode = stored?.code;
2359
+ const code = FILE_ERROR_CODES.find((known) => known === storedCode) ?? "PROCESSING_FAILED";
2360
+ return new FileUploadError(
2361
+ code,
2362
+ `Asset ${subject}: ${stored?.message ?? "File processing failed"}`,
2363
+ {
2364
+ retriable: stored?.retriable === true,
2365
+ context: { processingError: stored }
2366
+ }
2367
+ );
2368
+ }
2369
+ __name(processingFailure, "processingFailure");
2253
2370
  async function computeSha256Hex(blob) {
2254
2371
  try {
2255
2372
  const subtle = globalThis.crypto?.subtle;
@@ -2265,11 +2382,17 @@ async function computeSha256Hex(blob) {
2265
2382
  }
2266
2383
  __name(computeSha256Hex, "computeSha256Hex");
2267
2384
  var _AssetFileBuilder = class _AssetFileBuilder {
2268
- constructor(httpClient, assetType, entryUid, defaultLocale) {
2385
+ /**
2386
+ * @param locale - The client's locale, sent when a call names none: the function
2387
+ * `TypedRelyIOClient` hands over, or a locale fixed for this builder
2388
+ */
2389
+ constructor(httpClient, assetType, entryUid, locale) {
2269
2390
  this.httpClient = httpClient;
2270
2391
  this.assetType = assetType;
2271
2392
  this.entryUid = entryUid;
2272
- this.defaultLocale = defaultLocale;
2393
+ /** The client's locale, asked on every call rather than copied when this builder was taken. */
2394
+ __publicField(this, "clientLocale");
2395
+ this.clientLocale = readClientLocale(locale);
2273
2396
  }
2274
2397
  /**
2275
2398
  * The path parameters every `/file/*` route on this builder takes.
@@ -2282,7 +2405,16 @@ var _AssetFileBuilder = class _AssetFileBuilder {
2282
2405
  return { contentType: this.assetType, id: this.entryUid };
2283
2406
  }
2284
2407
  withLocale(locale) {
2285
- return locale ?? this.defaultLocale;
2408
+ return locale ?? this.clientLocale();
2409
+ }
2410
+ /**
2411
+ * The `locale` body field of a multipart call: the session's, else the client's. The API finds
2412
+ * the binding by it, so a session a `withLocale()` client opened must name the same locale on
2413
+ * every later call or its `objectKey` is refused as another binding's.
2414
+ */
2415
+ localeField(locale) {
2416
+ const sent = this.withLocale(locale);
2417
+ return hasText(sent) ? { locale: sent } : {};
2286
2418
  }
2287
2419
  /**
2288
2420
  * Issue a single upload target — an R2 presigned PUT (image/file) or a
@@ -2341,7 +2473,7 @@ var _AssetFileBuilder = class _AssetFileBuilder {
2341
2473
  uploadId: target.uploadId,
2342
2474
  objectKey: target.objectKey,
2343
2475
  partNumber: target.partNumber,
2344
- ...hasText(target.locale) ? { locale: target.locale } : {}
2476
+ ...this.localeField(target.locale)
2345
2477
  }
2346
2478
  });
2347
2479
  }
@@ -2352,7 +2484,7 @@ var _AssetFileBuilder = class _AssetFileBuilder {
2352
2484
  body: {
2353
2485
  uploadId: target.uploadId,
2354
2486
  objectKey: target.objectKey,
2355
- ...hasText(target.locale) ? { locale: target.locale } : {}
2487
+ ...this.localeField(target.locale)
2356
2488
  }
2357
2489
  });
2358
2490
  return payload.parts;
@@ -2365,7 +2497,7 @@ var _AssetFileBuilder = class _AssetFileBuilder {
2365
2497
  uploadId: target.uploadId,
2366
2498
  objectKey: target.objectKey,
2367
2499
  parts: target.parts.map((p) => ({ partNumber: p.partNumber, etag: p.etag })),
2368
- ...hasText(target.locale) ? { locale: target.locale } : {}
2500
+ ...this.localeField(target.locale)
2369
2501
  }
2370
2502
  });
2371
2503
  }
@@ -2376,7 +2508,7 @@ var _AssetFileBuilder = class _AssetFileBuilder {
2376
2508
  body: {
2377
2509
  uploadId: target.uploadId,
2378
2510
  objectKey: target.objectKey,
2379
- ...hasText(target.locale) ? { locale: target.locale } : {}
2511
+ ...this.localeField(target.locale)
2380
2512
  }
2381
2513
  });
2382
2514
  }
@@ -2396,6 +2528,25 @@ var _AssetFileBuilder = class _AssetFileBuilder {
2396
2528
  { path: this.target, query: params }
2397
2529
  );
2398
2530
  }
2531
+ /**
2532
+ * Read where this file's upload got to — `status`, and the processing stage, progress and error
2533
+ * the pipeline recorded — whatever the asset entry is published in.
2534
+ *
2535
+ * An asset is a draft until its file is ready, and stays one when its file fails, so a key that
2536
+ * reads published content finds no entry for an upload in flight or one that failed. This read
2537
+ * answers for the binding itself. It takes the capability the upload calls take, so a read-only
2538
+ * key is refused with `ReadOnlyKeyError`, and it never returns the entry's fields.
2539
+ */
2540
+ async status(options = {}) {
2541
+ const params = {};
2542
+ const locale = this.withLocale(options.locale);
2543
+ if (hasText(locale)) params.locale = locale;
2544
+ return this.httpClient.fetchPayload(
2545
+ "/v1/{contentType}/{id}/file",
2546
+ "get",
2547
+ { path: this.target, query: params }
2548
+ );
2549
+ }
2399
2550
  /**
2400
2551
  * Update the binding's delivery visibility (token-based; no byte relocation —
2401
2552
  * a downgrade purges the gateway cache). Returns the refreshed asset entry.
@@ -2434,14 +2585,31 @@ var _AssetFileBuilder = class _AssetFileBuilder {
2434
2585
  });
2435
2586
  }
2436
2587
  /**
2437
- * Poll the asset entry until its file binding reaches `ready` (resolves with
2438
- * the ready entry) or `failed` (throws). Times out after `timeoutMs`.
2588
+ * Poll the asset entry until its file binding reaches `ready`, and resolve with the ready entry.
2589
+ *
2590
+ * Each poll reads the entry through the key's lens. When that finds nothing — a key that reads
2591
+ * published content, and an asset that is a draft until its file is ready and stays one when it
2592
+ * fails — the poll reads the binding itself ({@link status}), so a failed upload rejects as one
2593
+ * rather than running out the limit (REL-1481).
2594
+ *
2595
+ * @throws {FileUploadError} When processing failed. `code` is the processing code the service
2596
+ * stored — `UNSUPPORTED_CODEC`, `CORRUPT_FILE` and so on — or `PROCESSING_FAILED` where it
2597
+ * stored none this SDK knows; `context.processingError` holds what it stored, and `retryable`
2598
+ * is its `retriable` hint.
2599
+ * @throws {TimeoutError} When `timeoutMs` passes first. The file may still become ready; the
2600
+ * error's `context.timeout` is the limit and `context.fileStatus` the status last seen. A file
2601
+ * that is `ready` in an asset the key's lens never shows — created with `publish: false`, or
2602
+ * awaiting a workflow — ends here too, and the message says so.
2603
+ * @throws {ReadOnlyKeyError} When the key's lens does not show the asset and the key may not read
2604
+ * its binding either: nothing it can read would ever report the file.
2439
2605
  */
2440
2606
  async waitUntilReady(options = {}) {
2607
+ assertWaitOptions(options);
2441
2608
  const timeoutMs = options.timeoutMs ?? 6e4;
2442
2609
  const pollIntervalMs = options.pollIntervalMs ?? 1500;
2443
2610
  const locale = this.withLocale(options.locale);
2444
2611
  const deadline = Date.now() + timeoutMs;
2612
+ const subject = `${this.assetType}/${this.entryUid}`;
2445
2613
  for (; ; ) {
2446
2614
  const builder = new ContentByIdBuilder(
2447
2615
  this.httpClient,
@@ -2451,15 +2619,24 @@ var _AssetFileBuilder = class _AssetFileBuilder {
2451
2619
  );
2452
2620
  if (hasText(locale)) builder.locale(locale);
2453
2621
  const entry = await builder.execute();
2454
- const status = entry?.file?.status;
2455
- if (entry && status === "ready") return entry;
2456
- if (status === "failed") {
2457
- const message = entry?.file?.processing?.error?.message ?? "File processing failed";
2458
- throw new Error(`Asset ${this.assetType}/${this.entryUid}: ${message}`);
2622
+ let status;
2623
+ if (entry === null) {
2624
+ const binding = await this.status({ ...hasText(locale) ? { locale } : {} });
2625
+ status = binding.status;
2626
+ if (status === "failed") throw processingFailure(subject, binding.processing?.error);
2627
+ } else {
2628
+ status = entry.file?.status;
2629
+ if (status === "ready") return entry;
2630
+ if (status === "failed") throw processingFailure(subject, entry.file?.processing?.error);
2459
2631
  }
2460
2632
  if (Date.now() >= deadline) {
2461
- throw new Error(
2462
- `Timed out after ${timeoutMs}ms waiting for ${this.assetType}/${this.entryUid} (status: ${status ?? "unknown"})`
2633
+ 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" : "";
2634
+ throw new TimeoutError(
2635
+ `Timed out after ${timeoutMs}ms waiting for ${subject} (status: ${status ?? "unknown"})${unseen}`,
2636
+ timeoutMs,
2637
+ void 0,
2638
+ void 0,
2639
+ { fileStatus: status }
2463
2640
  );
2464
2641
  }
2465
2642
  await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
@@ -2469,11 +2646,14 @@ var _AssetFileBuilder = class _AssetFileBuilder {
2469
2646
  __name(_AssetFileBuilder, "AssetFileBuilder");
2470
2647
  var AssetFileBuilder = _AssetFileBuilder;
2471
2648
  var _AssetUploadBuilder = class _AssetUploadBuilder {
2472
- constructor(httpClient, assetType, file, defaultLocale) {
2649
+ /**
2650
+ * @param locale - The client's locale, the upload's when `.locale()` names none: the function
2651
+ * `TypedRelyIOClient` hands over, or a locale fixed for this upload
2652
+ */
2653
+ constructor(httpClient, assetType, file, locale) {
2473
2654
  this.httpClient = httpClient;
2474
2655
  this.assetType = assetType;
2475
2656
  this.file = file;
2476
- this.defaultLocale = defaultLocale;
2477
2657
  __publicField(this, "_fileName");
2478
2658
  __publicField(this, "_visibility");
2479
2659
  __publicField(this, "_locale");
@@ -2485,6 +2665,9 @@ var _AssetUploadBuilder = class _AssetUploadBuilder {
2485
2665
  __publicField(this, "_onProgress");
2486
2666
  __publicField(this, "_validate");
2487
2667
  __publicField(this, "_maxPartRetries", 3);
2668
+ /** The client's locale, read once when the upload starts. */
2669
+ __publicField(this, "clientLocale");
2670
+ this.clientLocale = readClientLocale(locale);
2488
2671
  }
2489
2672
  /** Subscribe to client → storage upload progress (single PUT + multipart). */
2490
2673
  onProgress(callback) {
@@ -2534,8 +2717,14 @@ var _AssetUploadBuilder = class _AssetUploadBuilder {
2534
2717
  this._slug = slug;
2535
2718
  return this;
2536
2719
  }
2537
- /** Wait until the file binding becomes `ready` before resolving. */
2720
+ /**
2721
+ * Wait until the file binding becomes `ready` before resolving.
2722
+ *
2723
+ * @throws {Error} When `timeoutMs` or `pollIntervalMs` is one the wait cannot honour — refused
2724
+ * here, before anything is uploaded, rather than after the bytes are in.
2725
+ */
2538
2726
  waitForReady(options = true) {
2727
+ if (typeof options === "object") assertWaitOptions(options);
2539
2728
  if (options === false) {
2540
2729
  this._waitForReady = false;
2541
2730
  } else {
@@ -2550,7 +2739,8 @@ var _AssetUploadBuilder = class _AssetUploadBuilder {
2550
2739
  if (!fileName) {
2551
2740
  throw new Error('A file name is required: pass a File, or call .filename("name.ext")');
2552
2741
  }
2553
- const locale = this._locale ?? this.defaultLocale;
2742
+ const clientLocale = this.clientLocale();
2743
+ const locale = this._locale ?? clientLocale;
2554
2744
  if (this._validate) {
2555
2745
  const violations = validateFile(this.file, this._validate);
2556
2746
  if (violations.length > 0) {
@@ -2561,7 +2751,7 @@ var _AssetUploadBuilder = class _AssetUploadBuilder {
2561
2751
  }
2562
2752
  }
2563
2753
  const hash = await computeSha256Hex(this.file);
2564
- const client = new AssetClient(this.httpClient, this.assetType, this.defaultLocale);
2754
+ const client = new AssetClient(this.httpClient, this.assetType, clientLocale);
2565
2755
  const created = await client.create(this._content, {
2566
2756
  ...hasText(this._slug) ? { slug: this._slug } : {},
2567
2757
  ...hasText(locale) ? { locale } : {},
@@ -2712,17 +2902,26 @@ var _AssetUploadBuilder = class _AssetUploadBuilder {
2712
2902
  __name(_AssetUploadBuilder, "AssetUploadBuilder");
2713
2903
  var AssetUploadBuilder = _AssetUploadBuilder;
2714
2904
  var _AssetClient = class _AssetClient {
2715
- constructor(httpClient, assetType, defaultLocale) {
2905
+ /**
2906
+ * @param locale - The client's locale: the function `TypedRelyIOClient` hands over, asked on
2907
+ * every call, or a locale fixed for this client
2908
+ */
2909
+ constructor(httpClient, assetType, locale) {
2716
2910
  this.httpClient = httpClient;
2717
2911
  this.assetType = assetType;
2718
- this.defaultLocale = defaultLocale;
2912
+ /** The client's locale, asked on every call rather than copied when this client was taken. */
2913
+ __publicField(this, "clientLocale");
2914
+ this.clientLocale = readClientLocale(locale);
2719
2915
  }
2720
- /** Create a query builder for this asset type. */
2916
+ /**
2917
+ * Create a query builder for this asset type. It resolves asset reads; its conditions name the
2918
+ * asset type's fields, which the `file` and `urls` blocks are not.
2919
+ */
2721
2920
  query() {
2722
2921
  return new ContentQueryBuilder(
2723
2922
  this.httpClient,
2724
2923
  this.assetType,
2725
- this.defaultLocale
2924
+ this.clientLocale
2726
2925
  );
2727
2926
  }
2728
2927
  /** Find an asset entry by id (or slug) with fluent options. */
@@ -2731,7 +2930,7 @@ var _AssetClient = class _AssetClient {
2731
2930
  this.httpClient,
2732
2931
  this.assetType,
2733
2932
  id,
2734
- this.defaultLocale
2933
+ this.clientLocale
2735
2934
  );
2736
2935
  }
2737
2936
  /** Fetch a single asset entry by id (or slug), or `null` if not found. */
@@ -2752,28 +2951,34 @@ var _AssetClient = class _AssetClient {
2752
2951
  * Create a new asset entry as a draft. The binary is uploaded separately via
2753
2952
  * `file(uid)` (or use the high-level `upload()` helper), and the entry
2754
2953
  * auto-publishes when its file becomes ready (unless `publish: false`).
2954
+ * `locale` defaults to the client's own (`withLocale()`).
2755
2955
  */
2756
2956
  async create(data, options) {
2757
- const { idempotencyKey, ...rest } = options ?? {};
2957
+ const { idempotencyKey, locale, ...rest } = options ?? {};
2958
+ const sent = callLocale(locale, this.clientLocale());
2758
2959
  return this.httpClient.fetchPayload(
2759
2960
  "/v1/{contentType}",
2760
2961
  "post",
2761
2962
  {
2762
2963
  path: { contentType: this.assetType },
2763
- body: { content: data, ...rest },
2964
+ body: { content: data, ...rest, ...sent === void 0 ? {} : { locale: sent } },
2764
2965
  headers: writeHeaders({ idempotencyKey })
2765
2966
  }
2766
2967
  );
2767
2968
  }
2768
- /** Update an asset entry's metadata fields (and/or slug). */
2969
+ /**
2970
+ * Update an asset entry's metadata fields (and/or slug). `locale` defaults to the client's own
2971
+ * (`withLocale()`).
2972
+ */
2769
2973
  async update(id, data, options) {
2770
- const { idempotencyKey, ifMatch, ...rest } = options ?? {};
2974
+ const { idempotencyKey, ifMatch, locale, ...rest } = options ?? {};
2975
+ const sent = callLocale(locale, this.clientLocale());
2771
2976
  return this.httpClient.fetchPayload(
2772
2977
  "/v1/{contentType}/{id}",
2773
2978
  "patch",
2774
2979
  {
2775
2980
  path: { contentType: this.assetType, id },
2776
- body: { content: data, ...rest },
2981
+ body: { content: data, ...rest, ...sent === void 0 ? {} : { locale: sent } },
2777
2982
  headers: writeHeaders({ idempotencyKey, ifMatch })
2778
2983
  }
2779
2984
  );
@@ -2787,21 +2992,24 @@ var _AssetClient = class _AssetClient {
2787
2992
  }
2788
2993
  /** Get the file sub-resource builder for a specific asset entry. */
2789
2994
  file(entryUid) {
2790
- return new AssetFileBuilder(this.httpClient, this.assetType, entryUid, this.defaultLocale);
2995
+ return new AssetFileBuilder(this.httpClient, this.assetType, entryUid, this.clientLocale);
2791
2996
  }
2792
2997
  /** Start a high-level upload (create → presign → PUT → confirm). */
2793
2998
  upload(file) {
2794
- return new AssetUploadBuilder(this.httpClient, this.assetType, file, this.defaultLocale);
2999
+ return new AssetUploadBuilder(this.httpClient, this.assetType, file, this.clientLocale);
2795
3000
  }
2796
3001
  /** Get the asset type id. */
2797
3002
  getAssetType() {
2798
3003
  return this.assetType;
2799
3004
  }
2800
- /** Get the current default locale. */
3005
+ /** Get the default locale as it is now: the client's, or the one `withLocale()` fixed. */
2801
3006
  getDefaultLocale() {
2802
- return this.defaultLocale;
3007
+ return this.clientLocale();
2803
3008
  }
2804
- /** Create a new AssetClient for this type with a different default locale. */
3009
+ /**
3010
+ * Create a new AssetClient for this type with a different default locale, fixed: an update of the
3011
+ * client's locale (`updateConfig({ locale })`) does not reach it.
3012
+ */
2805
3013
  withLocale(locale) {
2806
3014
  return new _AssetClient(this.httpClient, this.assetType, locale);
2807
3015
  }
@@ -2817,12 +3025,18 @@ __name(isAssetEntry, "isAssetEntry");
2817
3025
 
2818
3026
  // src/content/singleton-client.ts
2819
3027
  var _SingletonClient = class _SingletonClient {
2820
- constructor(httpClient, singletonKey, defaultLocale) {
3028
+ /**
3029
+ * @param locale - The client's locale: the function `TypedRelyIOClient` hands over, asked on
3030
+ * every call, or a locale fixed for this client
3031
+ */
3032
+ constructor(httpClient, singletonKey, locale) {
2821
3033
  this.httpClient = httpClient;
2822
3034
  this.singletonKey = singletonKey;
2823
- this.defaultLocale = defaultLocale;
2824
3035
  __publicField(this, "includeFields");
2825
3036
  __publicField(this, "localeValue");
3037
+ /** The client's locale, asked on every call rather than copied when this client was taken. */
3038
+ __publicField(this, "clientLocale");
3039
+ this.clientLocale = readClientLocale(locale);
2826
3040
  }
2827
3041
  /**
2828
3042
  * Include reference fields in the response
@@ -2862,7 +3076,7 @@ var _SingletonClient = class _SingletonClient {
2862
3076
  async fetchWithTag(options) {
2863
3077
  const query = {};
2864
3078
  const includeValue = options?.include ?? this.includeFields;
2865
- const localeValueFinal = options?.locale ?? this.localeValue ?? this.defaultLocale;
3079
+ const localeValueFinal = options?.locale ?? this.localeValue ?? this.clientLocale();
2866
3080
  if (includeValue) query.include = includeValue.join(",");
2867
3081
  if (localeValueFinal != null && localeValueFinal !== "") query.locale = localeValueFinal;
2868
3082
  const response = await this.httpClient.send(
@@ -2876,11 +3090,12 @@ var _SingletonClient = class _SingletonClient {
2876
3090
  * Update singleton content. The API auto-publishes on PATCH — there is no
2877
3091
  * separate publish route; calling `update()` writes and publishes.
2878
3092
  * @param data - The content fields to update
2879
- * @param locale - Optional locale for localized content (overrides fluent locale)
3093
+ * @param locale - Optional locale for localized content (overrides the fluent locale, which
3094
+ * overrides the client's own)
2880
3095
  * @param options - `If-Match` / `Idempotency-Key`, both declared on this route
2881
3096
  */
2882
3097
  async update(data, locale, options) {
2883
- const finalLocale = locale ?? this.localeValue;
3098
+ const finalLocale = this.localeOf(locale);
2884
3099
  return this.httpClient.fetchPayload(
2885
3100
  "/v1/singletons/{key}",
2886
3101
  "patch",
@@ -2909,15 +3124,17 @@ var _SingletonClient = class _SingletonClient {
2909
3124
  * Get version history for one locale of the singleton.
2910
3125
  *
2911
3126
  * Versions are numbered per locale, so this is one locale's timeline: `locale` defaults to the
2912
- * schema default, no fallback is applied, and a locale with no history returns an empty array
2913
- * rather than another locale's rows. Every row names the locale it belongs to.
3127
+ * fluent `.locale()`, then the client's own (`withLocale()`), then the schema default; no
3128
+ * fallback is applied, and a locale with no history returns an empty array rather than another
3129
+ * locale's rows. Every row names the locale it belongs to.
2914
3130
  */
2915
3131
  async versions(options) {
3132
+ const locale = this.localeOf(options?.locale);
2916
3133
  const payload = await this.httpClient.fetchPayload("/v1/singletons/{key}/versions", "get", {
2917
3134
  path: { key: this.singletonKey },
2918
3135
  query: {
2919
3136
  limit: options?.limit ?? 10,
2920
- ...options?.locale === void 0 ? {} : { locale: options.locale }
3137
+ ...locale === void 0 ? {} : { locale }
2921
3138
  }
2922
3139
  });
2923
3140
  return payload.versions;
@@ -2925,20 +3142,25 @@ var _SingletonClient = class _SingletonClient {
2925
3142
  /**
2926
3143
  * Get a specific version of the singleton.
2927
3144
  *
2928
- * `locale` defaults to the schema default. Version numbers are per locale, so version 2 is
2929
- * different content in each one.
3145
+ * `locale` defaults to the fluent `.locale()`, then the client's own (`withLocale()`), then the
3146
+ * schema default. Version numbers are per locale, so version 2 is different content in each one.
2930
3147
  */
2931
3148
  async version(versionNumber, options) {
3149
+ const locale = this.localeOf(options?.locale);
2932
3150
  try {
2933
3151
  return await this.httpClient.fetchPayload("/v1/singletons/{key}/versions/{versionNumber}", "get", {
2934
3152
  path: { key: this.singletonKey, versionNumber: String(versionNumber) },
2935
- query: options?.locale === void 0 ? void 0 : { locale: options.locale }
3153
+ query: locale === void 0 ? void 0 : { locale }
2936
3154
  });
2937
3155
  } catch (error) {
2938
3156
  if (isResourceNotFound(error)) return null;
2939
3157
  throw error;
2940
3158
  }
2941
3159
  }
3160
+ /** The locale a write or history read sends: the call's, the fluent one, else the client's. */
3161
+ localeOf(named) {
3162
+ return callLocale(named ?? this.localeValue, this.clientLocale());
3163
+ }
2942
3164
  /**
2943
3165
  * Get the singleton key
2944
3166
  */
@@ -2959,12 +3181,18 @@ var SingletonClient = _SingletonClient;
2959
3181
 
2960
3182
  // src/content/settings-client.ts
2961
3183
  var _SettingsClient = class _SettingsClient {
2962
- constructor(httpClient, groupId, defaultLocale) {
3184
+ /**
3185
+ * @param locale - The client's locale: the function `TypedRelyIOClient` hands over, asked on
3186
+ * every call, or a locale fixed for this client
3187
+ */
3188
+ constructor(httpClient, groupId, locale) {
2963
3189
  this.httpClient = httpClient;
2964
3190
  this.groupId = groupId;
2965
- this.defaultLocale = defaultLocale;
2966
3191
  __publicField(this, "includeFields");
2967
3192
  __publicField(this, "localeValue");
3193
+ /** The client's locale, asked on every call rather than copied when this client was taken. */
3194
+ __publicField(this, "clientLocale");
3195
+ this.clientLocale = readClientLocale(locale);
2968
3196
  }
2969
3197
  /**
2970
3198
  * Include reference fields in the response
@@ -2996,12 +3224,20 @@ var _SettingsClient = class _SettingsClient {
2996
3224
  }
2997
3225
  /**
2998
3226
  * Update settings group
3227
+ *
3228
+ * The fields travel as `content` beside an optional `locale`, the body the route validates
3229
+ * (`UpdateSettingsDto`); any other top-level key is refused `REQUEST_INVALID`.
3230
+ *
2999
3231
  * @param settings - The settings fields to update
3000
- * @param locale - Optional locale for localized settings (overrides fluent locale)
3232
+ * @param locale - Optional locale for localized settings (overrides the fluent locale, which
3233
+ * overrides the client's own)
3001
3234
  */
3002
3235
  async update(settings, locale, options) {
3003
- const finalLocale = locale ?? this.localeValue;
3004
- const body = finalLocale != null && finalLocale !== "" ? { ...settings, locale: finalLocale } : settings;
3236
+ const finalLocale = this.localeOf(locale);
3237
+ const body = {
3238
+ content: settings,
3239
+ ...finalLocale != null && finalLocale !== "" ? { locale: finalLocale } : {}
3240
+ };
3005
3241
  return this.httpClient.fetchPayload(
3006
3242
  "/v1/settings/{groupId}",
3007
3243
  "patch",
@@ -3016,7 +3252,7 @@ var _SettingsClient = class _SettingsClient {
3016
3252
  async fetchWithTag(options) {
3017
3253
  const query = {};
3018
3254
  const includeValue = options?.include ?? this.includeFields;
3019
- const localeValueFinal = options?.locale ?? this.localeValue ?? this.defaultLocale;
3255
+ const localeValueFinal = options?.locale ?? this.localeValue ?? this.clientLocale();
3020
3256
  if (includeValue) query.include = includeValue.join(",");
3021
3257
  if (localeValueFinal != null && localeValueFinal !== "") query.locale = localeValueFinal;
3022
3258
  const response = await this.httpClient.send(
@@ -3028,16 +3264,20 @@ var _SettingsClient = class _SettingsClient {
3028
3264
  }
3029
3265
  /**
3030
3266
  * Reset settings to default values
3031
- * @param locale - Optional locale for localized settings
3267
+ *
3268
+ * The route reads its locale from `?locale=` and takes no body, so that is where it is sent.
3269
+ *
3270
+ * @param locale - Optional locale for localized settings (overrides the fluent locale, which
3271
+ * overrides the client's own)
3032
3272
  */
3033
3273
  async reset(locale, options) {
3034
- const finalLocale = locale ?? this.localeValue;
3274
+ const finalLocale = this.localeOf(locale);
3035
3275
  return this.httpClient.fetchPayload(
3036
3276
  "/v1/settings/{groupId}/reset",
3037
3277
  "post",
3038
3278
  {
3039
3279
  path: { groupId: this.groupId },
3040
- body: finalLocale != null && finalLocale !== "" ? { locale: finalLocale } : void 0,
3280
+ query: finalLocale != null && finalLocale !== "" ? { locale: finalLocale } : void 0,
3041
3281
  headers: writeHeaders(options)
3042
3282
  }
3043
3283
  );
@@ -3068,15 +3308,17 @@ var _SettingsClient = class _SettingsClient {
3068
3308
  * Get version history for one locale of the settings group.
3069
3309
  *
3070
3310
  * Versions are numbered per locale, so this is one locale's timeline: `locale` defaults to the
3071
- * schema default, no fallback is applied, and a locale with no history returns an empty array
3072
- * rather than another locale's rows. Every row names the locale it belongs to.
3311
+ * fluent `.locale()`, then the client's own (`withLocale()`), then the schema default; no
3312
+ * fallback is applied, and a locale with no history returns an empty array rather than another
3313
+ * locale's rows. Every row names the locale it belongs to.
3073
3314
  */
3074
3315
  async versions(options) {
3316
+ const locale = this.localeOf(options?.locale);
3075
3317
  const payload = await this.httpClient.fetchPayload("/v1/settings/{groupId}/versions", "get", {
3076
3318
  path: { groupId: this.groupId },
3077
3319
  query: {
3078
3320
  limit: options?.limit ?? 10,
3079
- ...options?.locale === void 0 ? {} : { locale: options.locale }
3321
+ ...locale === void 0 ? {} : { locale }
3080
3322
  }
3081
3323
  });
3082
3324
  return payload.versions;
@@ -3084,20 +3326,25 @@ var _SettingsClient = class _SettingsClient {
3084
3326
  /**
3085
3327
  * Get a specific version of the settings.
3086
3328
  *
3087
- * `locale` defaults to the schema default. Version numbers are per locale, so version 2 is
3088
- * different content in each one.
3329
+ * `locale` defaults to the fluent `.locale()`, then the client's own (`withLocale()`), then the
3330
+ * schema default. Version numbers are per locale, so version 2 is different content in each one.
3089
3331
  */
3090
3332
  async version(versionNumber, options) {
3333
+ const locale = this.localeOf(options?.locale);
3091
3334
  try {
3092
3335
  return await this.httpClient.fetchPayload("/v1/settings/{groupId}/versions/{versionNumber}", "get", {
3093
3336
  path: { groupId: this.groupId, versionNumber: String(versionNumber) },
3094
- query: options?.locale === void 0 ? void 0 : { locale: options.locale }
3337
+ query: locale === void 0 ? void 0 : { locale }
3095
3338
  });
3096
3339
  } catch (error) {
3097
3340
  if (isResourceNotFound(error)) return null;
3098
3341
  throw error;
3099
3342
  }
3100
3343
  }
3344
+ /** The locale a write or history read sends: the call's, the fluent one, else the client's. */
3345
+ localeOf(named) {
3346
+ return callLocale(named ?? this.localeValue, this.clientLocale());
3347
+ }
3101
3348
  /**
3102
3349
  * Get the settings group ID
3103
3350
  */
@@ -3119,15 +3366,18 @@ var SettingsClient = _SettingsClient;
3119
3366
  // src/search/search-client.ts
3120
3367
  var _SearchBuilder = class _SearchBuilder {
3121
3368
  /**
3122
- * @param defaultLocale - The client's locale (`withLocale()`), sent when `.locale()` names none
3369
+ * @param locale - The client's locale (`withLocale()`, `updateConfig({ locale })`), sent when
3370
+ * `.locale()` names none: the function `TypedRelyIOClient` hands over, or a locale fixed for it
3123
3371
  */
3124
- constructor(httpClient, query, defaultLocale) {
3372
+ constructor(httpClient, query, locale) {
3125
3373
  this.httpClient = httpClient;
3126
- this.defaultLocale = defaultLocale;
3127
3374
  __publicField(this, "query");
3128
3375
  __publicField(this, "searchOptions", {});
3129
3376
  __publicField(this, "requestHeaders");
3377
+ /** The client's locale, asked when the query is sent rather than copied when it was built. */
3378
+ __publicField(this, "clientLocale");
3130
3379
  this.query = query;
3380
+ this.clientLocale = readClientLocale(locale);
3131
3381
  }
3132
3382
  /**
3133
3383
  * Headers this search adds to the client's own, e.g. `X-RelyIO-Dataset` to search another
@@ -3158,10 +3408,12 @@ var _SearchBuilder = class _SearchBuilder {
3158
3408
  return this;
3159
3409
  }
3160
3410
  /**
3161
- * Add filter conditions
3411
+ * Add filter conditions, on the fields the content types declare. A condition on `_metadata` is
3412
+ * refused here, before anything is sent.
3162
3413
  * @param conditions - Filter conditions
3163
3414
  */
3164
3415
  where(conditions) {
3416
+ refuseMetadataConditions(conditions);
3165
3417
  this.searchOptions.where = {
3166
3418
  ...this.searchOptions.where,
3167
3419
  ...conditions
@@ -3269,7 +3521,7 @@ var _SearchBuilder = class _SearchBuilder {
3269
3521
  if (this.searchOptions.explain === true) {
3270
3522
  body.explain = true;
3271
3523
  }
3272
- const locale = this.searchOptions.locale ?? this.defaultLocale;
3524
+ const locale = this.searchOptions.locale ?? this.clientLocale();
3273
3525
  if (locale != null && locale !== "") {
3274
3526
  body.locale = locale;
3275
3527
  }
@@ -3297,14 +3549,17 @@ __name(_SearchBuilder, "SearchBuilder");
3297
3549
  var SearchBuilder = _SearchBuilder;
3298
3550
  var _AutocompleteBuilder = class _AutocompleteBuilder {
3299
3551
  /**
3300
- * @param defaultLocale - The client's locale (`withLocale()`), sent when `.locale()` names none
3552
+ * @param locale - The client's locale (`withLocale()`, `updateConfig({ locale })`), sent when
3553
+ * `.locale()` names none: the function `TypedRelyIOClient` hands over, or a locale fixed for it
3301
3554
  */
3302
- constructor(httpClient, query, defaultLocale) {
3555
+ constructor(httpClient, query, locale) {
3303
3556
  this.httpClient = httpClient;
3304
- this.defaultLocale = defaultLocale;
3305
3557
  __publicField(this, "query");
3306
3558
  __publicField(this, "autocompleteOptions", {});
3559
+ /** The client's locale, asked when the query is sent rather than copied when it was built. */
3560
+ __publicField(this, "clientLocale");
3307
3561
  this.query = query;
3562
+ this.clientLocale = readClientLocale(locale);
3308
3563
  }
3309
3564
  /**
3310
3565
  * Autocomplete within a content type
@@ -3330,10 +3585,12 @@ var _AutocompleteBuilder = class _AutocompleteBuilder {
3330
3585
  return this;
3331
3586
  }
3332
3587
  /**
3333
- * Add filter conditions
3588
+ * Add filter conditions, on the fields the content types declare. A condition on `_metadata` is
3589
+ * refused here, before anything is sent.
3334
3590
  * @param conditions - Filter conditions
3335
3591
  */
3336
3592
  where(conditions) {
3593
+ refuseMetadataConditions(conditions);
3337
3594
  this.autocompleteOptions.where = {
3338
3595
  ...this.autocompleteOptions.where,
3339
3596
  ...conditions
@@ -3380,7 +3637,7 @@ var _AutocompleteBuilder = class _AutocompleteBuilder {
3380
3637
  if (this.autocompleteOptions.limit !== void 0) {
3381
3638
  body.limit = this.autocompleteOptions.limit;
3382
3639
  }
3383
- const locale = this.autocompleteOptions.locale ?? this.defaultLocale;
3640
+ const locale = this.autocompleteOptions.locale ?? this.clientLocale();
3384
3641
  if (locale != null && locale !== "") {
3385
3642
  body.locale = locale;
3386
3643
  }
@@ -3398,15 +3655,18 @@ __name(_AutocompleteBuilder, "AutocompleteBuilder");
3398
3655
  var AutocompleteBuilder = _AutocompleteBuilder;
3399
3656
  var _SimilarBuilder = class _SimilarBuilder {
3400
3657
  /**
3401
- * @param defaultLocale - The client's locale (`withLocale()`), sent when `.locale()` names none
3658
+ * @param locale - The client's locale (`withLocale()`, `updateConfig({ locale })`), sent when
3659
+ * `.locale()` names none: the function `TypedRelyIOClient` hands over, or a locale fixed for it
3402
3660
  */
3403
- constructor(httpClient, sourceId, defaultLocale) {
3661
+ constructor(httpClient, sourceId, locale) {
3404
3662
  this.httpClient = httpClient;
3405
- this.defaultLocale = defaultLocale;
3406
3663
  __publicField(this, "sourceId");
3407
3664
  __publicField(this, "similarOptions", {});
3408
3665
  __publicField(this, "requestHeaders");
3666
+ /** The client's locale, asked when the read is sent rather than copied when it was built. */
3667
+ __publicField(this, "clientLocale");
3409
3668
  this.sourceId = sourceId;
3669
+ this.clientLocale = readClientLocale(locale);
3410
3670
  }
3411
3671
  /**
3412
3672
  * Headers this read adds to the client's own, e.g. `X-RelyIO-Dataset` to read another dataset
@@ -3434,17 +3694,12 @@ var _SimilarBuilder = class _SimilarBuilder {
3434
3694
  this.similarOptions.contentType = void 0;
3435
3695
  return this;
3436
3696
  }
3437
- /**
3438
- * Add filter conditions
3439
- * @param conditions - Filter conditions
3697
+ /*
3698
+ * No `where()` (REL-1452). Similar ranks from the source entry's stored vector, and the vector
3699
+ * index can pre-filter by content type and locale only — never by an entry field. The API used to
3700
+ * accept `where` here and apply nothing; it now refuses the property, so the builder does not
3701
+ * offer a filter that cannot be honoured. Narrow with `.in()`, `.across()` and `.locale()`.
3440
3702
  */
3441
- where(conditions) {
3442
- this.similarOptions.where = {
3443
- ...this.similarOptions.where,
3444
- ...conditions
3445
- };
3446
- return this;
3447
- }
3448
3703
  /**
3449
3704
  * Limit number of results
3450
3705
  * @param count - Maximum results
@@ -3478,13 +3733,10 @@ var _SimilarBuilder = class _SimilarBuilder {
3478
3733
  if (contentTypes != null) {
3479
3734
  body.contentTypes = contentTypes;
3480
3735
  }
3481
- if (this.similarOptions.where && Object.keys(this.similarOptions.where).length > 0) {
3482
- body.where = this.similarOptions.where;
3483
- }
3484
3736
  if (this.similarOptions.limit !== void 0) {
3485
3737
  body.limit = this.similarOptions.limit;
3486
3738
  }
3487
- const locale = this.similarOptions.locale ?? this.defaultLocale;
3739
+ const locale = this.similarOptions.locale ?? this.clientLocale();
3488
3740
  if (locale != null && locale !== "") {
3489
3741
  body.locale = locale;
3490
3742
  }
@@ -3512,41 +3764,36 @@ __name(_SimilarBuilder, "SimilarBuilder");
3512
3764
  var SimilarBuilder = _SimilarBuilder;
3513
3765
  var _SearchClient = class _SearchClient {
3514
3766
  /**
3515
- * @param defaultLocale - The client's locale (`withLocale()`): every builder here sends it when
3516
- * the query names none, as `content()`'s reads do
3767
+ * @param locale - The client's locale (`withLocale()`, `updateConfig({ locale })`): every builder
3768
+ * here sends it when the query names none, as `content()`'s reads do — the function
3769
+ * `TypedRelyIOClient` hands over, or a locale fixed for this client
3517
3770
  */
3518
- constructor(httpClient, defaultLocale) {
3771
+ constructor(httpClient, locale) {
3519
3772
  this.httpClient = httpClient;
3520
- this.defaultLocale = defaultLocale;
3773
+ /** The client's locale, handed to every builder here to ask when it sends. */
3774
+ __publicField(this, "clientLocale");
3775
+ this.clientLocale = readClientLocale(locale);
3521
3776
  }
3522
3777
  /**
3523
3778
  * Start a search query
3524
3779
  * @param query - Search query string
3525
3780
  */
3526
3781
  search(query) {
3527
- return new SearchBuilder(this.httpClient, query, this.defaultLocale);
3782
+ return new SearchBuilder(this.httpClient, query, this.clientLocale);
3528
3783
  }
3529
3784
  /**
3530
3785
  * Start an autocomplete query
3531
3786
  * @param query - Partial query to autocomplete
3532
3787
  */
3533
3788
  autocomplete(query) {
3534
- return new AutocompleteBuilder(this.httpClient, query, this.defaultLocale);
3789
+ return new AutocompleteBuilder(this.httpClient, query, this.clientLocale);
3535
3790
  }
3536
3791
  /**
3537
3792
  * Find content similar to a given entry
3538
3793
  * @param sourceId - Entry ID to find similar content for
3539
3794
  */
3540
3795
  similar(sourceId) {
3541
- return new SimilarBuilder(this.httpClient, sourceId, this.defaultLocale);
3542
- }
3543
- /**
3544
- * Quick search helper - searches all content types
3545
- * @param query - Search query
3546
- * @param limit - Max results (default 20)
3547
- */
3548
- async global(query, limit = 20) {
3549
- return this.search(query).across("*").limit(limit).execute();
3796
+ return new SimilarBuilder(this.httpClient, sourceId, this.clientLocale);
3550
3797
  }
3551
3798
  };
3552
3799
  __name(_SearchClient, "SearchClient");
@@ -3554,7 +3801,12 @@ var SearchClient = _SearchClient;
3554
3801
 
3555
3802
  // src/batch/batch-client.ts
3556
3803
  var _BatchBuilder = class _BatchBuilder {
3557
- constructor(httpClient) {
3804
+ /**
3805
+ * @param locale - The client's locale (`withLocale()`, `updateConfig({ locale })`), each
3806
+ * operation's when it names none: the function `TypedRelyIOClient` hands over, or a locale fixed
3807
+ * for this batch
3808
+ */
3809
+ constructor(httpClient, locale) {
3558
3810
  this.httpClient = httpClient;
3559
3811
  __publicField(this, "operations", []);
3560
3812
  __publicField(this, "defaultOptions", {
@@ -3564,6 +3816,17 @@ var _BatchBuilder = class _BatchBuilder {
3564
3816
  continueOnError: true
3565
3817
  });
3566
3818
  __publicField(this, "requestHeaders");
3819
+ /** The client's locale, asked as each operation is added rather than copied when this was built. */
3820
+ __publicField(this, "clientLocale");
3821
+ this.clientLocale = readClientLocale(locale);
3822
+ }
3823
+ /**
3824
+ * An operation's body with its locale: the one it names, else the client's as it is when the
3825
+ * operation is added.
3826
+ */
3827
+ localized(data) {
3828
+ const locale = callLocale(data.locale, this.clientLocale());
3829
+ return locale === void 0 ? data : { ...data, locale };
3567
3830
  }
3568
3831
  // ==========================================================================
3569
3832
  // ENTRY OPERATIONS
@@ -3579,7 +3842,7 @@ var _BatchBuilder = class _BatchBuilder {
3579
3842
  id,
3580
3843
  method: "POST",
3581
3844
  path: `/${contentType}`,
3582
- body: data
3845
+ body: this.localized(data)
3583
3846
  });
3584
3847
  return this;
3585
3848
  }
@@ -3599,7 +3862,7 @@ var _BatchBuilder = class _BatchBuilder {
3599
3862
  id,
3600
3863
  method: "PATCH",
3601
3864
  path: `/${contentType}/${entryId}`,
3602
- body: data
3865
+ body: this.localized(data)
3603
3866
  });
3604
3867
  return this;
3605
3868
  }
@@ -3619,7 +3882,7 @@ var _BatchBuilder = class _BatchBuilder {
3619
3882
  id,
3620
3883
  method: "PUT",
3621
3884
  path: `/${contentType}/${entryId}`,
3622
- body: data
3885
+ body: this.localized(data)
3623
3886
  });
3624
3887
  return this;
3625
3888
  }
@@ -3651,7 +3914,7 @@ var _BatchBuilder = class _BatchBuilder {
3651
3914
  id,
3652
3915
  method: "PATCH",
3653
3916
  path: `/_singletons/${singletonKey}`,
3654
- body: data
3917
+ body: this.localized(data)
3655
3918
  });
3656
3919
  return this;
3657
3920
  }
@@ -3666,7 +3929,7 @@ var _BatchBuilder = class _BatchBuilder {
3666
3929
  id,
3667
3930
  method: "POST",
3668
3931
  path: `/_singletons/${singletonKey}`,
3669
- body: data
3932
+ body: this.localized(data)
3670
3933
  });
3671
3934
  return this;
3672
3935
  }
@@ -3674,7 +3937,8 @@ var _BatchBuilder = class _BatchBuilder {
3674
3937
  // RAW OPERATIONS
3675
3938
  // ==========================================================================
3676
3939
  /**
3677
- * Add a raw operation (for advanced use cases)
3940
+ * Add a raw operation (for advanced use cases). It is sent exactly as written: the client's
3941
+ * locale is not added to it.
3678
3942
  * @param operation - The operation to add
3679
3943
  */
3680
3944
  raw(operation) {
@@ -3800,7 +4064,7 @@ var _BatchBuilder = class _BatchBuilder {
3800
4064
  * Clone the builder
3801
4065
  */
3802
4066
  clone() {
3803
- const cloned = new _BatchBuilder(this.httpClient);
4067
+ const cloned = new _BatchBuilder(this.httpClient, this.clientLocale);
3804
4068
  cloned.operations = [...this.operations];
3805
4069
  cloned.defaultOptions = { ...this.defaultOptions };
3806
4070
  cloned.requestHeaders = this.requestHeaders === void 0 ? void 0 : { ...this.requestHeaders };
@@ -3853,14 +4117,21 @@ var _BatchBuilder = class _BatchBuilder {
3853
4117
  __name(_BatchBuilder, "BatchBuilder");
3854
4118
  var BatchBuilder = _BatchBuilder;
3855
4119
  var _BatchClient = class _BatchClient {
3856
- constructor(httpClient) {
4120
+ /**
4121
+ * @param locale - The client's locale: the function `TypedRelyIOClient` hands over, or a locale
4122
+ * fixed for this client
4123
+ */
4124
+ constructor(httpClient, locale) {
3857
4125
  this.httpClient = httpClient;
4126
+ /** The client's locale, handed to every batch this builds to ask as operations are added. */
4127
+ __publicField(this, "clientLocale");
4128
+ this.clientLocale = readClientLocale(locale);
3858
4129
  }
3859
4130
  /**
3860
- * Create a new batch builder
4131
+ * Create a new batch builder, whose operations default to this client's locale
3861
4132
  */
3862
4133
  create() {
3863
- return new BatchBuilder(this.httpClient);
4134
+ return new BatchBuilder(this.httpClient, this.clientLocale);
3864
4135
  }
3865
4136
  /**
3866
4137
  * Quick bulk create entries
@@ -3911,15 +4182,18 @@ function withClientLocale(parameters, clientLocale) {
3911
4182
  __name(withClientLocale, "withClientLocale");
3912
4183
  var _AdhocQueryBuilder = class _AdhocQueryBuilder {
3913
4184
  /**
3914
- * @param defaultLocale - The client's locale (`withLocale()`), sent as the `locale` parameter
3915
- * when `.param('locale', …)` binds none
4185
+ * @param locale - The client's locale (`withLocale()`, `updateConfig({ locale })`), sent as the
4186
+ * `locale` parameter when `.param('locale', …)` binds none: the function `TypedRelyIOClient`
4187
+ * hands over, or a locale fixed for this builder
3916
4188
  */
3917
- constructor(httpClient, defaultLocale) {
4189
+ constructor(httpClient, locale) {
3918
4190
  this.httpClient = httpClient;
3919
- this.defaultLocale = defaultLocale;
3920
4191
  __publicField(this, "queryDef", {});
3921
4192
  __publicField(this, "parameters", {});
3922
4193
  __publicField(this, "options", {});
4194
+ /** The client's locale, asked when the query is sent rather than copied when it was built. */
4195
+ __publicField(this, "clientLocale");
4196
+ this.clientLocale = readClientLocale(locale);
3923
4197
  }
3924
4198
  // ==========================================================================
3925
4199
  // QUERY BLOCKS
@@ -3964,14 +4238,6 @@ var _AdhocQueryBuilder = class _AdhocQueryBuilder {
3964
4238
  this.queryDef.search = config;
3965
4239
  return this;
3966
4240
  }
3967
- /**
3968
- * Add similar block
3969
- * @param config - Similar configuration
3970
- */
3971
- similar(config) {
3972
- this.queryDef.similar = config;
3973
- return this;
3974
- }
3975
4241
  // ==========================================================================
3976
4242
  // FETCH MODIFIERS
3977
4243
  // ==========================================================================
@@ -4148,15 +4414,15 @@ var _AdhocQueryBuilder = class _AdhocQueryBuilder {
4148
4414
  * Execute the query
4149
4415
  */
4150
4416
  async execute() {
4151
- if (!this.queryDef.fetch && !this.queryDef.aggregate && !this.queryDef.search && !this.queryDef.similar) {
4417
+ if (!this.queryDef.fetch && !this.queryDef.aggregate && !this.queryDef.search) {
4152
4418
  throw new Error(
4153
- "Query must have at least one of: fetch, aggregate, search, or similar. Use .from(), .aggregate(), .search(), or .similar()"
4419
+ "Query must have at least one of: fetch, aggregate or search. Use .from(), .aggregate() or .search()"
4154
4420
  );
4155
4421
  }
4156
4422
  const body = {
4157
4423
  query: this.queryDef
4158
4424
  };
4159
- const parameters = withClientLocale(this.parameters, this.defaultLocale);
4425
+ const parameters = withClientLocale(this.parameters, this.clientLocale());
4160
4426
  if (parameters !== void 0 && Object.keys(parameters).length > 0) {
4161
4427
  body.parameters = parameters;
4162
4428
  }
@@ -4189,7 +4455,7 @@ var _AdhocQueryBuilder = class _AdhocQueryBuilder {
4189
4455
  * Clone the builder
4190
4456
  */
4191
4457
  clone() {
4192
- const cloned = new _AdhocQueryBuilder(this.httpClient, this.defaultLocale);
4458
+ const cloned = new _AdhocQueryBuilder(this.httpClient, this.clientLocale);
4193
4459
  cloned.queryDef = JSON.parse(JSON.stringify(this.queryDef));
4194
4460
  cloned.parameters = { ...this.parameters };
4195
4461
  cloned.options = { ...this.options };
@@ -4200,18 +4466,21 @@ __name(_AdhocQueryBuilder, "AdhocQueryBuilder");
4200
4466
  var AdhocQueryBuilder = _AdhocQueryBuilder;
4201
4467
  var _AdhocQueryClient = class _AdhocQueryClient {
4202
4468
  /**
4203
- * @param defaultLocale - The client's locale (`withLocale()`), sent as the `locale` parameter of
4204
- * every query that binds none
4469
+ * @param locale - The client's locale (`withLocale()`, `updateConfig({ locale })`), sent as the
4470
+ * `locale` parameter of every query that binds none: the function `TypedRelyIOClient` hands
4471
+ * over, or a locale fixed for this client
4205
4472
  */
4206
- constructor(httpClient, defaultLocale) {
4473
+ constructor(httpClient, locale) {
4207
4474
  this.httpClient = httpClient;
4208
- this.defaultLocale = defaultLocale;
4475
+ /** The client's locale, asked on every query rather than copied when this client was taken. */
4476
+ __publicField(this, "clientLocale");
4477
+ this.clientLocale = readClientLocale(locale);
4209
4478
  }
4210
4479
  /**
4211
4480
  * Create a new query builder
4212
4481
  */
4213
4482
  build() {
4214
- return new AdhocQueryBuilder(this.httpClient, this.defaultLocale);
4483
+ return new AdhocQueryBuilder(this.httpClient, this.clientLocale);
4215
4484
  }
4216
4485
  /**
4217
4486
  * Execute a query definition directly
@@ -4226,7 +4495,7 @@ var _AdhocQueryClient = class _AdhocQueryClient {
4226
4495
  const body = {
4227
4496
  query: definition
4228
4497
  };
4229
- const sent = withClientLocale(parameters, this.defaultLocale);
4498
+ const sent = withClientLocale(parameters, this.clientLocale());
4230
4499
  if (sent !== void 0 && Object.keys(sent).length > 0) {
4231
4500
  body.parameters = sent;
4232
4501
  }
@@ -4475,7 +4744,7 @@ var DEFAULT_CONFIG = {
4475
4744
  environment: "production",
4476
4745
  headers: {},
4477
4746
  browser: {
4478
- includeCredentials: true,
4747
+ includeCredentials: false,
4479
4748
  useRelativeUrls: false
4480
4749
  },
4481
4750
  cache: {
@@ -4507,45 +4776,42 @@ function validateConfig(config) {
4507
4776
  if (config.baseUrl != null && typeof config.baseUrl !== "string") {
4508
4777
  throw new Error("Base URL must be a string");
4509
4778
  }
4510
- if (config.timeout != null && (typeof config.timeout !== "number" || config.timeout < 0)) {
4511
- throw new Error("Timeout must be a positive number");
4512
- }
4513
- if (config.retryAttempts != null && (typeof config.retryAttempts !== "number" || config.retryAttempts < 0)) {
4514
- throw new Error("Retry attempts must be a positive number");
4779
+ if (config.timeout != null) assertRequestTimeout(config.timeout);
4780
+ if (config.retryAttempts != null && !(Number.isInteger(config.retryAttempts) && config.retryAttempts >= 1)) {
4781
+ throw new Error(
4782
+ `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)}`
4783
+ );
4515
4784
  }
4516
4785
  }
4517
4786
  __name(validateConfig, "validateConfig");
4518
4787
  var devOverrideNotified = false;
4519
4788
  function resolveConfig(userConfig) {
4520
4789
  validateConfig(userConfig);
4521
- const merged = {
4522
- ...DEFAULT_CONFIG,
4523
- ...userConfig,
4524
- headers: {
4525
- ...DEFAULT_CONFIG.headers,
4526
- ...userConfig.headers
4527
- },
4528
- browser: {
4529
- ...DEFAULT_CONFIG.browser,
4530
- ...userConfig.browser
4531
- },
4532
- cache: {
4533
- ...DEFAULT_CONFIG.cache,
4534
- ...userConfig.cache
4535
- },
4536
- realtime: {
4537
- ...DEFAULT_CONFIG.realtime,
4538
- ...userConfig.realtime
4539
- },
4540
- debug: {
4541
- ...DEFAULT_CONFIG.debug,
4542
- ...userConfig.debug
4543
- }
4544
- };
4545
- const dev = resolveDevOverride(userConfig.dev);
4790
+ return withHandshake(withDefaults(DEFAULT_CONFIG, userConfig, DEFAULT_CONFIG));
4791
+ }
4792
+ __name(resolveConfig, "resolveConfig");
4793
+ function resolveConfigUpdate(configured, update) {
4794
+ validateConfig(update);
4795
+ const base = update.apiKey === void 0 ? configured : withoutKeyHeader(configured);
4796
+ return withHandshake(withDefaults(base, update, DEFAULT_CONFIG));
4797
+ }
4798
+ __name(resolveConfigUpdate, "resolveConfigUpdate");
4799
+ function withConfiguredKey(configured, apiKey) {
4800
+ return { ...withoutKeyHeader(configured), apiKey };
4801
+ }
4802
+ __name(withConfiguredKey, "withConfiguredKey");
4803
+ function withoutKeyHeader(configured) {
4804
+ return { ...configured, headers: layerHeaders(configured.headers, { [API_KEY_HEADER]: null }) };
4805
+ }
4806
+ __name(withoutKeyHeader, "withoutKeyHeader");
4807
+ function withHandshake(configured) {
4808
+ const dev = resolveDevOverride(configured.dev);
4809
+ const merged = withDefaults(
4810
+ configured,
4811
+ dev.state === "active" ? { apiKey: dev.apiKey, dataset: dev.sandbox.datasetId } : {},
4812
+ DEFAULT_CONFIG
4813
+ );
4546
4814
  if (dev.state === "active") {
4547
- merged.apiKey = dev.apiKey;
4548
- merged.dataset = dev.sandbox.datasetId;
4549
4815
  if (!devOverrideNotified) {
4550
4816
  devOverrideNotified = true;
4551
4817
  console.info(
@@ -4563,30 +4829,67 @@ function resolveConfig(userConfig) {
4563
4829
  `[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.`
4564
4830
  );
4565
4831
  }
4566
- return { config: merged, sandbox: sandboxStateOf(dev) };
4832
+ return { config: merged, sandbox: sandboxStateOf(dev), configured };
4567
4833
  }
4568
- __name(resolveConfig, "resolveConfig");
4834
+ __name(withHandshake, "withHandshake");
4835
+ function withDefaults(base, given, defaults) {
4836
+ const merged = new Map(Object.entries(base));
4837
+ const givenValues = new Map(Object.entries(given ?? {}));
4838
+ const defaultValues = new Map(Object.entries(defaults));
4839
+ for (const key of /* @__PURE__ */ new Set([...merged.keys(), ...givenValues.keys()])) {
4840
+ const fallback = merged.get(key);
4841
+ const value = givenValues.get(key);
4842
+ const givenBag = isOptionBag(value) ? value : void 0;
4843
+ if (isOptionBag(fallback) && key === "headers") {
4844
+ merged.set(key, layerHeaders(fallback, givenBag));
4845
+ } else if (isOptionBag(fallback)) {
4846
+ const defaultBag = defaultValues.get(key);
4847
+ merged.set(key, withDefaults(fallback, givenBag, isOptionBag(defaultBag) ? defaultBag : {}));
4848
+ } else if (value === null) {
4849
+ if (defaultValues.has(key)) merged.set(key, defaultValues.get(key));
4850
+ else merged.delete(key);
4851
+ } else if (value !== void 0) {
4852
+ merged.set(key, value);
4853
+ }
4854
+ }
4855
+ return Object.fromEntries(merged);
4856
+ }
4857
+ __name(withDefaults, "withDefaults");
4858
+ function isOptionBag(value) {
4859
+ return typeof value === "object" && value !== null && !Array.isArray(value);
4860
+ }
4861
+ __name(isOptionBag, "isOptionBag");
4569
4862
 
4570
4863
  // src/client/create-client.ts
4571
4864
  var API_VERSION_SEGMENT = "v1";
4572
4865
  var _TypedRelyIOClient = class _TypedRelyIOClient {
4573
4866
  constructor(config) {
4867
+ /**
4868
+ * The one HTTP client every sub-client sends through. `updateConfig()` and `setApiKey()` change it
4869
+ * in place rather than replacing it, so a sub-client taken before either sends through the change.
4870
+ */
4574
4871
  __publicField(this, "httpClient");
4575
4872
  __publicField(this, "config");
4873
+ /**
4874
+ * The configuration as `createClient()`, `setApiKey()` and `updateConfig()` gave it, before the
4875
+ * `relyio dev` handshake: what the next update is laid over, and what `withLocale()` starts from.
4876
+ */
4877
+ __publicField(this, "configured");
4576
4878
  __publicField(this, "_sandbox");
4577
- __publicField(this, "_search");
4578
- __publicField(this, "_batch");
4579
- __publicField(this, "_adhocQuery");
4580
4879
  __publicField(this, "_schema");
4880
+ /**
4881
+ * The client's locale as it is when a call is made. Every sub-client and builder is handed this
4882
+ * rather than a copy of the locale, so one taken before `updateConfig({ locale })` sends the new
4883
+ * locale, or none after `locale: null` (REL-1453).
4884
+ */
4885
+ __publicField(this, "currentLocale", /* @__PURE__ */ __name(() => this.config.locale, "currentLocale"));
4581
4886
  const { schema, ...clientConfig } = config;
4582
4887
  this._schema = schema;
4583
4888
  const resolved = resolveConfig(clientConfig);
4584
4889
  this.config = resolved.config;
4890
+ this.configured = resolved.configured;
4585
4891
  this._sandbox = resolved.sandbox;
4586
4892
  this.httpClient = new HttpClient(this.config);
4587
- this._search = new SearchClient(this.httpClient, this.config.locale);
4588
- this._batch = new BatchClient(this.httpClient);
4589
- this._adhocQuery = new AdhocQueryClient(this.httpClient, this.config.locale);
4590
4893
  }
4591
4894
  // ============================================================================
4592
4895
  // CORE CLIENTS
@@ -4615,10 +4918,10 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
4615
4918
  return this._sandbox;
4616
4919
  }
4617
4920
  /**
4618
- * Get the ad-hoc query client
4921
+ * Get the ad-hoc query client. Every query it sends is in the client's locale as it is then.
4619
4922
  */
4620
4923
  get query() {
4621
- return this._adhocQuery;
4924
+ return new AdhocQueryClient(this.httpClient, this.currentLocale);
4622
4925
  }
4623
4926
  // ============================================================================
4624
4927
  // CONTENT OPERATIONS
@@ -4638,7 +4941,7 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
4638
4941
  * ```
4639
4942
  */
4640
4943
  content(contentType) {
4641
- return new ContentClient(this.httpClient, contentType, this.config.locale);
4944
+ return new ContentClient(this.httpClient, contentType, this.currentLocale);
4642
4945
  }
4643
4946
  /**
4644
4947
  * Get an asset client for a specific asset type.
@@ -4659,7 +4962,7 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
4659
4962
  * ```
4660
4963
  */
4661
4964
  asset(assetType) {
4662
- return new AssetClient(this.httpClient, assetType, this.config.locale);
4965
+ return new AssetClient(this.httpClient, assetType, this.currentLocale);
4663
4966
  }
4664
4967
  /**
4665
4968
  * Get a singleton client for a specific singleton key
@@ -4674,7 +4977,7 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
4674
4977
  * ```
4675
4978
  */
4676
4979
  singleton(key) {
4677
- return new SingletonClient(this.httpClient, key, this.config.locale);
4980
+ return new SingletonClient(this.httpClient, key, this.currentLocale);
4678
4981
  }
4679
4982
  /**
4680
4983
  * Get a settings client for a specific settings group
@@ -4688,7 +4991,7 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
4688
4991
  * ```
4689
4992
  */
4690
4993
  settings(groupId) {
4691
- return new SettingsClient(this.httpClient, groupId, this.config.locale);
4994
+ return new SettingsClient(this.httpClient, groupId, this.currentLocale);
4692
4995
  }
4693
4996
  // ============================================================================
4694
4997
  // SEARCH OPERATIONS
@@ -4710,7 +5013,7 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
4710
5013
  * ```
4711
5014
  */
4712
5015
  search(query) {
4713
- return this._search.search(query);
5016
+ return this.searchClient().search(query);
4714
5017
  }
4715
5018
  /**
4716
5019
  * Start an autocomplete query for search suggestions
@@ -4726,7 +5029,7 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
4726
5029
  * ```
4727
5030
  */
4728
5031
  autocomplete(query) {
4729
- return this._search.autocomplete(query);
5032
+ return this.searchClient().autocomplete(query);
4730
5033
  }
4731
5034
  /**
4732
5035
  * Find content similar to a given entry using vector search
@@ -4741,13 +5044,17 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
4741
5044
  * ```
4742
5045
  */
4743
5046
  similar(sourceId) {
4744
- return this._search.similar(sourceId);
5047
+ return this.searchClient().similar(sourceId);
4745
5048
  }
4746
5049
  // ============================================================================
4747
5050
  // BATCH OPERATIONS
4748
5051
  // ============================================================================
4749
5052
  /**
4750
- * Start a batch operation for bulk create/update/delete
5053
+ * Start a batch operation for bulk create/update/delete.
5054
+ *
5055
+ * By default each operation lands or fails on its own. `atomic: true` makes the batch one
5056
+ * transaction, which the API accepts only when every operation is the same kind of write and none
5057
+ * writes a singleton that does not exist yet.
4751
5058
  *
4752
5059
  * @example
4753
5060
  * ```typescript
@@ -4756,13 +5063,23 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
4756
5063
  * .update('product', 'entry_abc', { content: { price: 899 } })
4757
5064
  * .delete('product', 'entry_xyz')
4758
5065
  * .upsertSingleton('homepage', { content: { hero: 'Welcome!' } })
4759
- * .execute({ atomic: true })
5066
+ * .execute()
4760
5067
  *
4761
5068
  * console.log(result.summary.succeeded)
5069
+ *
5070
+ * // All or nothing: one kind of write
5071
+ * await relyio.batch()
5072
+ * .update('product', 'entry_abc', { content: { price: 899 } })
5073
+ * .update('product', 'entry_def', { content: { price: 799 } })
5074
+ * .execute({ atomic: true })
4762
5075
  * ```
4763
5076
  */
4764
5077
  batch() {
4765
- return this._batch.create();
5078
+ return new BatchClient(this.httpClient, this.currentLocale).create();
5079
+ }
5080
+ /** The search client, whose builders send the client's locale as it is when each one is sent. */
5081
+ searchClient() {
5082
+ return new SearchClient(this.httpClient, this.currentLocale);
4766
5083
  }
4767
5084
  // ============================================================================
4768
5085
  // CHANGES FEED
@@ -4836,7 +5153,7 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
4836
5153
  * ```
4837
5154
  */
4838
5155
  adhoc() {
4839
- return this._adhocQuery.build();
5156
+ return this.query.build();
4840
5157
  }
4841
5158
  /**
4842
5159
  * Execute a predefined query by name (from schema)
@@ -4921,16 +5238,53 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
4921
5238
  }
4922
5239
  }
4923
5240
  /**
4924
- * Update the configuration and reinitialize all clients
5241
+ * Change some of the client's options and keep the rest.
5242
+ *
5243
+ * What you pass is laid over the configuration the client holds, field by field:
5244
+ *
5245
+ * - an option you name replaces the value the client holds;
5246
+ * - an option left out, or given as `undefined`, keeps the value the client holds;
5247
+ * - an option with no default given as `null` is cleared — `locale`, `dataset`, `apiKey`, `dev`,
5248
+ * `fetch`, `browser.onCorsError`, `realtime.wsUrl`, `debug.logger` — so the client runs as though
5249
+ * it had never been given one. An option with a default takes no `null`;
5250
+ * - a bag of options you name — `browser`, `cache`, `realtime`, `debug` — keeps every field of it
5251
+ * you do not name, so `updateConfig({ browser: { includeCredentials: true } })` keeps a
5252
+ * configured `onCorsError`;
5253
+ * - `headers` is laid over the headers the client sends, a name matched without regard to case: a
5254
+ * header you name is added or replaces every spelling of it, every other one is still sent, and a
5255
+ * header given as `null` is no longer sent;
5256
+ * - naming `apiKey` — a key, or `null` — replaces every key the client holds, one given in
5257
+ * `headers` included.
5258
+ *
5259
+ * The configuration it is laid over is what `createClient()`, `setApiKey()` and earlier updates
5260
+ * gave the client. The `relyio dev` handshake is read again, as `createClient()` reads it: while a
5261
+ * sandbox is active its key and dataset are sent over the configured ones, and once it has gone
5262
+ * the configured ones are.
5263
+ *
5264
+ * The client's one HTTP client is changed in place, so every request from now on is sent as the
5265
+ * update says — including one from a `content()`, `asset()`, `singleton()` or `settings()`
5266
+ * sub-client, a `query` client, or a builder such as `search()` or `batch()`, taken before the
5267
+ * call. The locale reaches them too: each reads the client's locale when it sends, so a new
5268
+ * `locale` is the default of its next request and `locale: null` leaves that request with none. A
5269
+ * locale the call names, and a sub-client's own `withLocale()`, still win over it. A client made
5270
+ * by `withLocale()` is a client of its own: an update of this one does not reach it.
5271
+ *
5272
+ * @example
5273
+ * ```typescript
5274
+ * const relyio = createClient({ apiKey, headers: { 'X-Tenant': 'acme' }, browser: { onCorsError } })
5275
+ *
5276
+ * relyio.updateConfig({ headers: { 'X-Trace': traceId } }) // X-Tenant is still sent
5277
+ * relyio.updateConfig({ browser: { includeCredentials: true } }) // onCorsError is kept
5278
+ * relyio.updateConfig({ headers: { 'X-Tenant': null } }) // X-Tenant is no longer sent
5279
+ * relyio.updateConfig({ locale: null }) // reads name no locale of the client's
5280
+ * ```
4925
5281
  */
4926
- updateConfig(newConfig) {
4927
- const resolved = resolveConfig({ ...this.config, ...newConfig });
5282
+ updateConfig(update) {
5283
+ const resolved = resolveConfigUpdate(this.configured, update);
4928
5284
  this.config = resolved.config;
5285
+ this.configured = resolved.configured;
4929
5286
  this._sandbox = resolved.sandbox;
4930
- this.httpClient = new HttpClient(this.config);
4931
- this._search = new SearchClient(this.httpClient, this.config.locale);
4932
- this._batch = new BatchClient(this.httpClient);
4933
- this._adhocQuery = new AdhocQueryClient(this.httpClient, this.config.locale);
5287
+ this.httpClient.configure(this.config);
4934
5288
  }
4935
5289
  /**
4936
5290
  * Get the current configuration
@@ -4939,11 +5293,31 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
4939
5293
  return { ...this.config };
4940
5294
  }
4941
5295
  /**
4942
- * Set the API key
5296
+ * Replace the API key every later request sends as `X-API-Key`.
5297
+ *
5298
+ * It reaches every request this client makes from now on, including those of a `content()`,
5299
+ * `asset()` or other sub-client taken before the call, and `getConfig().apiKey` reports it. A key
5300
+ * given in `headers`, under any spelling, is replaced with it and does not come back on a later
5301
+ * `updateConfig()`. A client already derived with `withLocale()` is a client of its own and keeps
5302
+ * its key.
5303
+ *
5304
+ * An empty or blank key is no key, as it is when passed to `createClient`: the next request
5305
+ * rejects with `MissingApiKeyError` and nothing is sent.
5306
+ *
5307
+ * Under an active `relyio dev` sandbox the sandbox's scoped key stands, as it does over the key
5308
+ * passed to `createClient` — replacing the configured key does not retarget the client at the
5309
+ * sandbox's dataset with a key the sandbox did not issue.
5310
+ *
5311
+ * @example
5312
+ * ```typescript
5313
+ * const relyio = createClient({ apiKey: process.env.RELYIO_API_KEY! })
5314
+ * relyio.setApiKey(rotatedKey) // the next read sends `X-API-Key: <rotatedKey>`
5315
+ * ```
4943
5316
  */
4944
5317
  setApiKey(apiKey) {
4945
- this.config.apiKey = apiKey;
4946
- this.httpClient.setAuthToken(apiKey);
5318
+ this.configured = withConfiguredKey(this.configured, apiKey);
5319
+ if (this._sandbox.state === "active") return;
5320
+ this.httpClient.setApiKey(apiKey);
4947
5321
  }
4948
5322
  /**
4949
5323
  * Get the HTTP client instance (for advanced usage)
@@ -4957,9 +5331,12 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
4957
5331
  /**
4958
5332
  * Create a locale-scoped client
4959
5333
  *
4960
- * Every read the returned client builds — `content()`, `asset()`, `singleton()`, `settings()`,
4961
- * `search()`, `similar()`, `autocomplete()`, `executeQuery()` and the ad-hoc queries — is in this
4962
- * locale unless the call names one of its own, which wins.
5334
+ * Every call the returned client builds is in this locale unless the call names one of its own,
5335
+ * which wins: every read — `content()`, `asset()`, `singleton()`, `settings()`, `search()`,
5336
+ * `similar()`, `autocomplete()`, `executeQuery()` and the ad-hoc queries — and every write and
5337
+ * version-history read — `create()` and `update()`, the singleton and settings writes, the asset
5338
+ * file writes, `versions()` and `version()`, and each operation `batch()` builds. `restore()`
5339
+ * always names its locale; a delete, and a `batch().raw()` operation, carry none of the client's.
4963
5340
  *
4964
5341
  * Type-safe when schema is provided: autocompletes valid locale codes!
4965
5342
  *
@@ -4967,10 +5344,11 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
4967
5344
  * ```typescript
4968
5345
  * const spanish = relyio.withLocale('es-ES') // ← Autocompletes with schema!
4969
5346
  *
4970
- * // Reads use Spanish
5347
+ * // Reads and writes use Spanish
4971
5348
  * const products = await spanish.content('product').query().execute()
4972
5349
  * const homepage = await spanish.singleton('homepage').fetch()
4973
5350
  * const results = await spanish.search('zapatos').execute()
5351
+ * await spanish.content('product').update(id, { title: 'Zapatos' })
4974
5352
  *
4975
5353
  * // Original client unchanged
4976
5354
  * const englishProducts = await relyio.content('product').query().execute()
@@ -4978,7 +5356,7 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
4978
5356
  */
4979
5357
  withLocale(locale) {
4980
5358
  const newConfig = {
4981
- ...this.getConfig(),
5359
+ ...this.configured,
4982
5360
  locale,
4983
5361
  schema: this._schema
4984
5362
  };
@@ -5027,6 +5405,6 @@ function createClient(config) {
5027
5405
  }
5028
5406
  __name(createClient, "createClient");
5029
5407
 
5030
- export { AdhocQueryBuilder, AdhocQueryClient, AssetClient, AssetFileBuilder, AssetUploadBuilder, AuthenticationError, AuthorizationError, AutocompleteBuilder, BaseValidationError, BatchBuilder, BatchClient, ChangesExpiredError, ConnectionError, ContentByIdBuilder, ContentClient, ContentQueryBuilder, ErrorMapper, FileUploadError, FileValidationError, HttpClient, InvalidApiKeyError, InvalidFieldTypeError, InvalidPayloadError, MethodNotAllowedError, MissingApiKeyError, NetworkError, PayloadTooLargeError, PlanFeatureUnavailableError, RateLimitError, ReadOnlyKeyError, RequiredFieldError, ResourceConflictError, ResourceExpiredError, ResourceNotFoundError, SDKError, SchemaValidationError, SearchBuilder, SearchClient, ServerError, ServiceIndeterminateError, SettingsClient, SimilarBuilder, SingletonClient, TimeoutError, TransactionRolledBackError, TypedRelyIOClient, UnknownContractError, UsageQuotaExceededError, ValidationError, createClient, entityTagOf, isAborted, isAssetEntry, isResourceNotFound, matchRoute, serviceInstructedDelayMs, sleep, validateFile };
5031
- //# sourceMappingURL=chunk-7ABNXVWL.js.map
5032
- //# sourceMappingURL=chunk-7ABNXVWL.js.map
5408
+ export { AdhocQueryBuilder, AdhocQueryClient, AssetClient, AssetFileBuilder, AssetUploadBuilder, AuthorizationError, AutocompleteBuilder, BaseValidationError, BatchBuilder, BatchClient, ChangesExpiredError, ConnectionError, ContentByIdBuilder, ContentClient, ContentQueryBuilder, ErrorMapper, FileUploadError, FileValidationError, HttpClient, InvalidApiKeyError, InvalidPayloadError, MethodNotAllowedError, MissingApiKeyError, NetworkError, PayloadTooLargeError, PlanFeatureUnavailableError, RateLimitError, ReadOnlyKeyError, ResourceConflictError, ResourceExpiredError, ResourceNotFoundError, SDKError, SearchBuilder, SearchClient, ServerError, ServiceIndeterminateError, SettingsClient, SimilarBuilder, SingletonClient, TimeoutError, TransactionRolledBackError, TypedRelyIOClient, UnknownContractError, UsageQuotaExceededError, ValidationError, createClient, entityTagOf, isAborted, isAssetEntry, isResourceNotFound, matchRoute, serviceInstructedDelayMs, sleep, validateFile };
5409
+ //# sourceMappingURL=chunk-GYHAVYND.js.map
5410
+ //# sourceMappingURL=chunk-GYHAVYND.js.map