@relyio/sdk 0.6.0 → 0.8.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 +400 -0
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/{chunk-DY2XT7OF.cjs → chunk-72UK5U7T.cjs} +70 -60
  4. package/dist/{chunk-I3ESNANB.js → chunk-H6IBQSMM.js} +2 -2
  5. package/dist/{chunk-7ABNXVWL.js → chunk-QFHVF3YF.js} +1617 -404
  6. package/dist/{chunk-HT6NFRM2.cjs → chunk-TZL374LS.cjs} +2 -2
  7. package/dist/{chunk-GCMQUM6N.cjs → chunk-XIE7WJV3.cjs} +1621 -407
  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 +5 -4
  11. package/dist/client/index.d.ts +5 -4
  12. package/dist/client/index.js +2 -1
  13. package/dist/{index-d1Kz1e19.d.cts → define-content-type-cEGjIwT5.d.cts} +1859 -733
  14. package/dist/{index-yPbhZc6q.d.ts → define-content-type-cEGjIwT5.d.ts} +1859 -733
  15. package/dist/{define-schema-1kr2HIzN.d.cts → define-schema-BPj2srgh.d.ts} +189 -85
  16. package/dist/{define-schema-ChvAzY8H.d.ts → define-schema-DgbJ5Kwn.d.cts} +189 -85
  17. package/dist/index-B_rIWO_l.d.cts +860 -0
  18. package/dist/index-Cv4tmcrw.d.ts +860 -0
  19. package/dist/{index-Df1VsfJR.d.cts → index-DBoPPlT1.d.cts} +684 -277
  20. package/dist/{index-XqoGUaAY.d.ts → index-yRJ2IqM_.d.ts} +684 -277
  21. package/dist/index.cjs +148 -124
  22. package/dist/index.d.cts +174 -64
  23. package/dist/index.d.ts +174 -64
  24. package/dist/index.js +43 -18
  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 +2 -1
  36. package/dist/define-content-type-CDB3kyeA.d.cts +0 -1173
  37. package/dist/define-content-type-CDB3kyeA.d.ts +0 -1173
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var chunkLSRPMNPT_cjs = require('./chunk-LSRPMNPT.cjs');
4
+ var chunkTZL374LS_cjs = require('./chunk-TZL374LS.cjs');
4
5
  var chunkMWMSFIWN_cjs = require('./chunk-MWMSFIWN.cjs');
5
6
 
6
7
  /* RelyIO SDK - Built with ❤️ by the RelyIO Team */
@@ -232,18 +233,6 @@ chunkMWMSFIWN_cjs.__name(_UnknownContractError, "UnknownContractError");
232
233
  var UnknownContractError = _UnknownContractError;
233
234
 
234
235
  // src/errors/auth-errors.ts
235
- var _AuthenticationError = class _AuthenticationError extends SDKError {
236
- constructor(message = "Authentication failed", statusCode = 401, cause, context) {
237
- super(message, statusCode, cause, context);
238
- chunkMWMSFIWN_cjs.__publicField(this, "code", "AUTHENTICATION_ERROR");
239
- chunkMWMSFIWN_cjs.__publicField(this, "retryable", false);
240
- }
241
- getUserMessage() {
242
- return "Authentication failed. Please check your API key and try again.";
243
- }
244
- };
245
- chunkMWMSFIWN_cjs.__name(_AuthenticationError, "AuthenticationError");
246
- var AuthenticationError = _AuthenticationError;
247
236
  var _AuthorizationError = class _AuthorizationError extends SDKError {
248
237
  constructor(message = "Authorization failed", statusCode = 403, cause, context) {
249
238
  super(message, statusCode, cause, context);
@@ -422,58 +411,45 @@ var _InvalidPayloadError = class _InvalidPayloadError extends BaseValidationErro
422
411
  };
423
412
  chunkMWMSFIWN_cjs.__name(_InvalidPayloadError, "InvalidPayloadError");
424
413
  var InvalidPayloadError = _InvalidPayloadError;
425
- var _SchemaValidationError = class _SchemaValidationError extends BaseValidationError {
426
- constructor(message = "Schema validation failed", violations = [], cause) {
427
- super(message, violations, 400, cause);
428
- chunkMWMSFIWN_cjs.__publicField(this, "code", "SCHEMA_VALIDATION_ERROR");
414
+
415
+ // ../../platform/api-contract/dist/chunk-PYZCLRXR.js
416
+ function defineRoute(route) {
417
+ if (route.kind === "raw" && route.standardShape === void 0) {
418
+ throw new Error("kind 'raw' requires a named StandardShape");
429
419
  }
430
- getUserMessage() {
431
- return "The data does not match the expected schema. Please check your content structure.";
420
+ if (route.kind === "raw" && route.success === 302 && !route.headers.some((header) => header.name === "Location")) {
421
+ throw new Error("a raw route declaring success 302 must declare its 'Location' header");
432
422
  }
433
- };
434
- chunkMWMSFIWN_cjs.__name(_SchemaValidationError, "SchemaValidationError");
435
- var SchemaValidationError = _SchemaValidationError;
436
- var _RequiredFieldError = class _RequiredFieldError extends BaseValidationError {
437
- constructor(fieldName, message, cause) {
438
- const violation = {
439
- field: fieldName,
440
- message: message ?? `Field "${fieldName}" is required`,
441
- code: "REQUIRED"
442
- };
443
- super(`Required field "${fieldName}" is missing`, [violation], 400, cause);
444
- chunkMWMSFIWN_cjs.__publicField(this, "code", "REQUIRED_FIELD_ERROR");
423
+ if (route.kind === "stream" && route.stream === void 0) {
424
+ throw new Error("kind 'stream' requires a StreamDeclaration");
445
425
  }
446
- getUserMessage() {
447
- const violation = this.violations[0];
448
- const fieldName = violation?.field;
449
- return `The field "${fieldName}" is required and cannot be empty.`;
426
+ if (route.kind === "collection" && route.page === void 0) {
427
+ throw new Error("kind 'collection' requires a page dialect");
450
428
  }
451
- };
452
- chunkMWMSFIWN_cjs.__name(_RequiredFieldError, "RequiredFieldError");
453
- var RequiredFieldError = _RequiredFieldError;
454
- var _InvalidFieldTypeError = class _InvalidFieldTypeError extends BaseValidationError {
455
- constructor(fieldName, expectedType, actualType, value, cause) {
456
- const violation = {
457
- field: fieldName,
458
- message: `Expected ${expectedType}, got ${actualType}`,
459
- code: "INVALID_TYPE",
460
- value
461
- };
462
- super(
463
- `Invalid type for field "${fieldName}": expected ${expectedType}, got ${actualType}`,
464
- [violation],
465
- 400,
466
- cause
429
+ if ((route.kind === "object" || route.kind === "collection") && route.negotiated !== void 0) {
430
+ refuseMismatchedAlternate(route, route.negotiated["text/event-stream"]);
431
+ }
432
+ return route;
433
+ }
434
+ chunkMWMSFIWN_cjs.__name(defineRoute, "defineRoute");
435
+ function refuseMismatchedAlternate(route, stream) {
436
+ if (stream.kind !== "stream") {
437
+ throw new Error("a negotiated 'text/event-stream' alternate must be kind 'stream'");
438
+ }
439
+ if (stream.stream.mediaType !== "text/event-stream") {
440
+ throw new Error(
441
+ `a negotiated 'text/event-stream' alternate declares media type "${stream.stream.mediaType}"`
467
442
  );
468
- chunkMWMSFIWN_cjs.__publicField(this, "code", "INVALID_FIELD_TYPE");
469
443
  }
470
- getUserMessage() {
471
- const violation = this.violations[0];
472
- return `The field "${violation?.field}" has an invalid type. ${violation?.message}.`;
444
+ const own = [...new Set(route.codes)].sort();
445
+ const alternate = [...new Set(stream.codes)].sort();
446
+ if (own.length !== alternate.length || own.some((code, index) => code !== alternate[index])) {
447
+ throw new Error(
448
+ `a negotiated alternate refuses with [${alternate.join(", ")}] while its route refuses with [${own.join(", ")}]; one route refuses with one set of codes on every transport`
449
+ );
473
450
  }
474
- };
475
- chunkMWMSFIWN_cjs.__name(_InvalidFieldTypeError, "InvalidFieldTypeError");
476
- var InvalidFieldTypeError = _InvalidFieldTypeError;
451
+ }
452
+ chunkMWMSFIWN_cjs.__name(refuseMismatchedAlternate, "refuseMismatchedAlternate");
477
453
 
478
454
  // ../../platform/api-contract/dist/index.js
479
455
  var HEADER_RETRY_AFTER = { name: "Retry-After" };
@@ -482,6 +458,169 @@ var HEADER_IF_MATCH = {
482
458
  name: "If-Match"};
483
459
  var HEADER_IDEMPOTENCY_KEY = {
484
460
  name: "Idempotency-Key"};
461
+ var FRESH_PARAMETER = "_fresh";
462
+ var VISITOR_LIVE_PATH = "/visitor/changes";
463
+ var VISITOR_QUERY = {
464
+ /** The publishable key. */
465
+ key: "key",
466
+ /** A dataset id or name the key reaches; absent, the key's default dataset. */
467
+ dataset: "dataset",
468
+ /** The cursor to continue from; absent, the socket starts now. */
469
+ since: "since",
470
+ /** Comma-separated content types to keep; absent keeps every type. */
471
+ types: "types",
472
+ /** Comma-separated entry ids to keep; absent keeps every entry. */
473
+ ids: "ids",
474
+ /** The poll's client id. Required on a poll, and not read on a socket. */
475
+ client: "client"
476
+ };
477
+ var VISITOR_CURSOR = /^[\w.~-]{1,256}$/;
478
+ var VISITOR_POLL_INTERVAL_MS = 15e3;
479
+ var VISITOR_KEEPALIVE = { request: "ping", response: "pong" };
480
+ var VISITOR_FRAMES = {
481
+ opened: "feed.opened",
482
+ changes: "feed.changes",
483
+ resync: "feed.resync"
484
+ };
485
+ defineRoute({
486
+ kind: "object",
487
+ headers: [HEADER_RETRY_AFTER],
488
+ codes: [
489
+ "REQUEST_INVALID",
490
+ "MISSING_API_KEY",
491
+ "INVALID_API_KEY",
492
+ "ACCESS_DENIED",
493
+ "METHOD_NOT_ALLOWED",
494
+ "SERVICE_INDETERMINATE",
495
+ "USAGE_QUOTA_EXCEEDED",
496
+ "RATE_LIMIT_EXCEEDED"
497
+ ]
498
+ });
499
+ var VISITOR_CLOSE_CODES = {
500
+ /** A query the door cannot read: a malformed `since`, filter, `dataset` or `client`. */
501
+ requestInvalid: 4400,
502
+ /** No publishable key, or one that is unknown, revoked, expired, a secret key or a preview key. */
503
+ keyRefused: 4401,
504
+ /** The page's `Origin` is absent, or not on the key's allowed origins. */
505
+ originRefused: 4403,
506
+ /** `dataset` names nothing the key reaches. */
507
+ datasetRefused: 4404,
508
+ /** The key's scope, the project's counter or the feed could not be reached just now. */
509
+ unavailable: 1013,
510
+ /**
511
+ * The project holds as many visitor connections as its plan allows — Starter's 100 — or its
512
+ * Organization is suspended. As HTTP's 402: more needs a bigger plan, or visitors leaving.
513
+ */
514
+ allowanceReached: 4402,
515
+ /** This client's address holds as many connections to the project as one address may. */
516
+ addressLimitReached: 4429,
517
+ /** The project is at its per-project safety ceiling — Pro's 50,000 — whatever it would pay for. */
518
+ ceilingReached: 4503
519
+ };
520
+ var VISITOR_CLOSE_RETRY = {
521
+ 4400: "stop",
522
+ 4401: "stop",
523
+ 4403: "stop",
524
+ 4404: "stop",
525
+ 1013: "retry",
526
+ 4402: "back-off",
527
+ 4429: "back-off",
528
+ 4503: "back-off"
529
+ };
530
+ var VISITOR_POLL_RETRY = {
531
+ REQUEST_INVALID: "stop",
532
+ MISSING_API_KEY: "stop",
533
+ INVALID_API_KEY: "stop",
534
+ ACCESS_DENIED: "stop",
535
+ METHOD_NOT_ALLOWED: "stop",
536
+ SERVICE_INDETERMINATE: "retry",
537
+ USAGE_QUOTA_EXCEEDED: "back-off",
538
+ RATE_LIMIT_EXCEEDED: "back-off"
539
+ };
540
+ var VISITOR_ADMISSION_RETRY_AFTER_SECONDS = 60;
541
+ function parseVisitorFrame(text) {
542
+ let value;
543
+ try {
544
+ value = JSON.parse(text);
545
+ } catch {
546
+ return null;
547
+ }
548
+ if (!isRecord2(value) || !isCursor(value.cursor)) return null;
549
+ switch (value.type) {
550
+ case VISITOR_FRAMES.opened: {
551
+ return hasOnly(value, ["type", "cursor"]) ? { type: VISITOR_FRAMES.opened, cursor: value.cursor } : null;
552
+ }
553
+ case VISITOR_FRAMES.changes: {
554
+ const changes = hasOnly(value, ["type", "changes", "cursor"]) ? readVisitorChanges(value.changes) : null;
555
+ return changes === null ? null : { type: VISITOR_FRAMES.changes, changes, cursor: value.cursor };
556
+ }
557
+ case VISITOR_FRAMES.resync: {
558
+ return hasOnly(value, ["type", "reason", "cursor"]) && isResyncReason(value.reason) ? { type: VISITOR_FRAMES.resync, reason: value.reason, cursor: value.cursor } : null;
559
+ }
560
+ default: {
561
+ return null;
562
+ }
563
+ }
564
+ }
565
+ chunkMWMSFIWN_cjs.__name(parseVisitorFrame, "parseVisitorFrame");
566
+ function readVisitorPollAnswer(value) {
567
+ if (!isRecord2(value) || !isCursor(value.cursor)) return null;
568
+ if (value.kind === "resync") {
569
+ return hasOnly(value, ["kind", "reason", "cursor"]) && isResyncReason(value.reason) ? { kind: "resync", reason: value.reason, cursor: value.cursor } : null;
570
+ }
571
+ if (value.kind !== "changes" || !hasOnly(value, ["kind", "changes", "cursor"])) return null;
572
+ const changes = readVisitorChanges(value.changes);
573
+ return changes === null ? null : { kind: "changes", changes, cursor: value.cursor };
574
+ }
575
+ chunkMWMSFIWN_cjs.__name(readVisitorPollAnswer, "readVisitorPollAnswer");
576
+ var CHANGE_KINDS = /* @__PURE__ */ new Set([
577
+ "published",
578
+ "updated",
579
+ "unpublished",
580
+ "deleted"
581
+ ]);
582
+ var RESYNC_REASONS = /* @__PURE__ */ new Set([
583
+ "retention",
584
+ "dataset-replaced",
585
+ "ahead-of-head",
586
+ "foreign-cursor",
587
+ "too-far-behind"
588
+ ]);
589
+ function isResyncReason(value) {
590
+ return typeof value === "string" && RESYNC_REASONS.has(value);
591
+ }
592
+ chunkMWMSFIWN_cjs.__name(isResyncReason, "isResyncReason");
593
+ function readVisitorChanges(value) {
594
+ if (!Array.isArray(value)) return null;
595
+ const changes = [];
596
+ for (const item of value) {
597
+ if (!isRecord2(item) || !hasOnly(item, ["entryId", "contentType", "locale", "kind"])) return null;
598
+ const { entryId, contentType, locale, kind } = item;
599
+ if (!isNonEmptyString2(entryId) || !isNonEmptyString2(contentType)) return null;
600
+ if (locale !== null && typeof locale !== "string") return null;
601
+ if (typeof kind !== "string" || !CHANGE_KINDS.has(kind)) return null;
602
+ changes.push({ entryId, contentType, locale, kind });
603
+ }
604
+ return changes;
605
+ }
606
+ chunkMWMSFIWN_cjs.__name(readVisitorChanges, "readVisitorChanges");
607
+ function isCursor(value) {
608
+ return typeof value === "string" && VISITOR_CURSOR.test(value);
609
+ }
610
+ chunkMWMSFIWN_cjs.__name(isCursor, "isCursor");
611
+ function isRecord2(value) {
612
+ return typeof value === "object" && value !== null && !Array.isArray(value);
613
+ }
614
+ chunkMWMSFIWN_cjs.__name(isRecord2, "isRecord2");
615
+ function isNonEmptyString2(value) {
616
+ return typeof value === "string" && value.length > 0;
617
+ }
618
+ chunkMWMSFIWN_cjs.__name(isNonEmptyString2, "isNonEmptyString2");
619
+ function hasOnly(value, keys) {
620
+ const present = Object.keys(value);
621
+ return present.length === keys.length && present.every((key) => keys.includes(key));
622
+ }
623
+ chunkMWMSFIWN_cjs.__name(hasOnly, "hasOnly");
485
624
 
486
625
  // ../../platform/api-contract/dist/client/index.js
487
626
  var _a;
@@ -750,6 +889,8 @@ var DECLARED_PATHS = {
750
889
  "/health/cache": true,
751
890
  "/health/live": true,
752
891
  "/health/ready": true,
892
+ "/internal/live/visitor-feed/{projectId}/{datasetId}/changes": true,
893
+ "/internal/live/visitor-key": true,
753
894
  "/internal/media/claim": true,
754
895
  "/internal/media/file-complete": true,
755
896
  "/internal/media/progress": true,
@@ -873,6 +1014,20 @@ function isAborted(signal) {
873
1014
  }
874
1015
  chunkMWMSFIWN_cjs.__name(isAborted, "isAborted");
875
1016
 
1017
+ // src/http/request-timeout.ts
1018
+ var MAX_TIMER_DELAY_MS = 2147483647;
1019
+ function isTimerDelay(value) {
1020
+ return typeof value === "number" && value >= 1 && value <= MAX_TIMER_DELAY_MS;
1021
+ }
1022
+ chunkMWMSFIWN_cjs.__name(isTimerDelay, "isTimerDelay");
1023
+ function assertRequestTimeout(timeout) {
1024
+ if (isTimerDelay(timeout)) return;
1025
+ throw new Error(
1026
+ `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)}`
1027
+ );
1028
+ }
1029
+ chunkMWMSFIWN_cjs.__name(assertRequestTimeout, "assertRequestTimeout");
1030
+
876
1031
  // src/errors/instructed-delay.ts
877
1032
  function serviceInstructedDelayMs(error, nowMs) {
878
1033
  if (error instanceof ServiceIndeterminateError) {
@@ -926,43 +1081,31 @@ var DefaultRetryPolicy = _DefaultRetryPolicy;
926
1081
  // src/http/http-client.ts
927
1082
  var _HttpClient = class _HttpClient {
928
1083
  constructor(config) {
929
- chunkMWMSFIWN_cjs.__publicField(this, "baseUrl");
930
- chunkMWMSFIWN_cjs.__publicField(this, "defaultHeaders");
931
- chunkMWMSFIWN_cjs.__publicField(this, "timeout");
932
- chunkMWMSFIWN_cjs.__publicField(this, "retryPolicy");
933
- chunkMWMSFIWN_cjs.__publicField(this, "debug");
934
- chunkMWMSFIWN_cjs.__publicField(this, "logger");
935
- chunkMWMSFIWN_cjs.__publicField(this, "config");
936
- this.config = config;
937
- this.baseUrl = config.baseUrl;
938
- this.timeout = config.timeout;
939
- this.debug = config.debug.enabled ?? false;
940
- this.logger = config.debug.logger;
941
- const apiKey = presentKey(config.apiKey);
942
- this.defaultHeaders = {
943
- "Content-Type": "application/json",
944
- ...apiKey === void 0 ? {} : { [API_KEY_HEADER]: apiKey },
945
- ...config.headers
946
- };
947
- if (config.dataset != null) {
948
- this.defaultHeaders["X-RelyIO-Dataset"] = config.dataset;
949
- }
950
- if (typeof window === "undefined") {
951
- const globalWithVersion = globalThis;
952
- this.defaultHeaders["User-Agent"] = `RelyIO-SDK/${globalWithVersion.__VERSION__ ?? "0.0.0"}`;
953
- }
954
- this.retryPolicy = new DefaultRetryPolicy({
955
- maxAttempts: config.retryAttempts,
956
- baseDelay: 1e3,
957
- maxDelay: 1e4,
958
- backoffFactor: 2,
959
- jitter: true
960
- });
1084
+ /** What every request reads off the configuration, derived by {@link configure} and nowhere else. */
1085
+ chunkMWMSFIWN_cjs.__publicField(this, "connection");
1086
+ this.connection = connectionFor(config);
1087
+ this.announce();
1088
+ }
1089
+ /**
1090
+ * Run on `config` from now on: every later request — whoever holds this client — is sent to its
1091
+ * `baseUrl`, with its headers, key and dataset, its timeout and retries, through its `fetch`, and
1092
+ * logged by its `debug` options.
1093
+ *
1094
+ * The client's own `updateConfig()` calls this rather than building a second client, so a
1095
+ * `content()` or other sub-client taken before the update sends through the update too. Anything
1096
+ * set on this client since it was last configured — `setDefaultHeaders()`, `setBaseUrl()` — is
1097
+ * replaced by what `config` says.
1098
+ */
1099
+ configure(config) {
1100
+ this.connection = connectionFor(config);
1101
+ this.announce();
1102
+ }
1103
+ announce() {
961
1104
  this.log("debug", "HttpClient initialized", {
962
- baseUrl: this.baseUrl,
963
- timeout: this.timeout,
964
- retryAttempts: config.retryAttempts,
965
- environment: typeof window === "undefined" ? "node" : "browser"
1105
+ baseUrl: this.connection.baseUrl,
1106
+ timeout: this.connection.timeout,
1107
+ retryAttempts: this.connection.config.retryAttempts,
1108
+ environment: chunkTZL374LS_cjs.isBrowser() ? "browser" : "node"
966
1109
  });
967
1110
  }
968
1111
  /**
@@ -999,19 +1142,24 @@ var _HttpClient = class _HttpClient {
999
1142
  *
1000
1143
  * The loop always makes one attempt. The incumbent's `while (attempt <
1001
1144
  * maxAttempts)` made none at `retryAttempts: 0` — a value the config validator
1002
- * accepts — and then dereferenced an unassigned `lastError`.
1145
+ * then accepted, and now refuses — and then dereferenced an unassigned
1146
+ * `lastError`. `retryAttempts` is the most attempts, the first included; an
1147
+ * `HttpClient` constructed directly is not validated, so the first attempt
1148
+ * stays unconditional.
1003
1149
  *
1004
1150
  * A request that would carry no `X-API-Key` makes none: it rejects with
1005
1151
  * `MissingApiKeyError` before `fetch` is called, is not retried, and names
1006
1152
  * `apiKey`. This is where a client built without a key learns it — the
1007
1153
  * constructor accepts one so that a module-scope client can be evaluated by a
1008
- * build that holds no key (REL-1120).
1154
+ * build that holds no key (REL-1120) — and so does one whose `setApiKey` was
1155
+ * given none.
1009
1156
  */
1010
1157
  async request(config) {
1158
+ if (config.timeout !== void 0) assertRequestTimeout(config.timeout);
1011
1159
  const mergedConfig = this.mergeRequestConfig(config);
1012
1160
  if (presentKey(headerValue(mergedConfig.headers, API_KEY_HEADER)) === void 0) {
1013
1161
  throw new MissingApiKeyError(
1014
- "`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.",
1162
+ "`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.",
1015
1163
  { url: mergedConfig.url, method: mergedConfig.method, sent: false }
1016
1164
  );
1017
1165
  }
@@ -1021,7 +1169,7 @@ var _HttpClient = class _HttpClient {
1021
1169
  // Show the final URL with query params
1022
1170
  });
1023
1171
  if (config.method === "DELETE" && mergedConfig.headers) {
1024
- delete mergedConfig.headers["Content-Type"];
1172
+ mergedConfig.headers = layerHeaders(mergedConfig.headers, { "Content-Type": null });
1025
1173
  }
1026
1174
  for (let attempt = 1; ; attempt++) {
1027
1175
  try {
@@ -1040,8 +1188,8 @@ var _HttpClient = class _HttpClient {
1040
1188
  attempt,
1041
1189
  url: mergedConfig.url
1042
1190
  });
1043
- if (this.retryPolicy.shouldRetry(attempt, failure, mergedConfig)) {
1044
- const delay = this.retryPolicy.delayFor(attempt, failure);
1191
+ if (this.connection.retryPolicy.shouldRetry(attempt, failure, mergedConfig)) {
1192
+ const delay = this.connection.retryPolicy.delayFor(attempt, failure);
1045
1193
  this.log("debug", "Retrying request", { attempt, delay });
1046
1194
  await sleep(delay, config.signal);
1047
1195
  continue;
@@ -1113,34 +1261,60 @@ var _HttpClient = class _HttpClient {
1113
1261
  });
1114
1262
  }
1115
1263
  /**
1116
- * Set default headers for all requests
1264
+ * Add headers to every later request, each replacing any header of the same name — the name
1265
+ * matched without regard to case — until the client is next configured.
1117
1266
  */
1118
1267
  setDefaultHeaders(headers) {
1119
- this.defaultHeaders = { ...this.defaultHeaders, ...headers };
1268
+ this.connection = {
1269
+ ...this.connection,
1270
+ defaultHeaders: layerHeaders(this.connection.defaultHeaders, headers)
1271
+ };
1120
1272
  }
1121
1273
  /**
1122
- * Set authentication token
1274
+ * Replace the key every later request sends as `X-API-Key`, and the `apiKey` the configuration
1275
+ * reports.
1276
+ *
1277
+ * The key is read the way construction reads it (`presentKey`): an absent, empty or blank key is
1278
+ * no key, so the header is dropped and the next request is refused with `MissingApiKeyError`
1279
+ * before anything is sent. A key header of any other spelling — one given in `headers` — goes
1280
+ * too, from the headers sent and from the headers the configuration reports, so the old key is
1281
+ * never sent beside the new one.
1282
+ *
1283
+ * The configuration is the one the owning `TypedRelyIOClient` holds (it hands its own to the
1284
+ * constructor), so its `getConfig().apiKey` — what a surface reads to decide a key's kind — is the
1285
+ * key sent from here on.
1286
+ *
1287
+ * This replaced `setAuthToken`, which added `Authorization: Bearer` beside an `X-API-Key` it
1288
+ * never touched: the API reads `X-API-Key` first, so the old key was the one used (REL-1385).
1123
1289
  */
1124
- setAuthToken(token) {
1125
- this.defaultHeaders.Authorization = `Bearer ${token}`;
1290
+ setApiKey(apiKey) {
1291
+ const { config } = this.connection;
1292
+ config.apiKey = apiKey;
1293
+ config.headers = layerHeaders(config.headers, { [API_KEY_HEADER]: null });
1294
+ this.connection = {
1295
+ ...this.connection,
1296
+ defaultHeaders: layerHeaders(this.connection.defaultHeaders, {
1297
+ [API_KEY_HEADER]: presentKey(apiKey) ?? null
1298
+ })
1299
+ };
1126
1300
  }
1127
1301
  /**
1128
1302
  * Get current default headers
1129
1303
  */
1130
1304
  getDefaultHeaders() {
1131
- return { ...this.defaultHeaders };
1305
+ return { ...this.connection.defaultHeaders };
1132
1306
  }
1133
1307
  /**
1134
1308
  * Update base URL
1135
1309
  */
1136
1310
  setBaseUrl(baseUrl) {
1137
- this.baseUrl = baseUrl;
1311
+ this.connection = { ...this.connection, baseUrl };
1138
1312
  }
1139
1313
  /**
1140
1314
  * Get current base URL
1141
1315
  */
1142
1316
  getBaseUrl() {
1143
- return this.baseUrl;
1317
+ return this.connection.baseUrl;
1144
1318
  }
1145
1319
  /**
1146
1320
  * One attempt.
@@ -1162,7 +1336,7 @@ var _HttpClient = class _HttpClient {
1162
1336
  const timeoutId = setTimeout(() => {
1163
1337
  timedOut = true;
1164
1338
  controller.abort();
1165
- }, config.timeout ?? this.timeout);
1339
+ }, config.timeout ?? this.connection.timeout);
1166
1340
  const onCallerAbort = /* @__PURE__ */ chunkMWMSFIWN_cjs.__name(() => controller.abort(), "onCallerAbort");
1167
1341
  caller?.addEventListener("abort", onCallerAbort, { once: true });
1168
1342
  try {
@@ -1172,14 +1346,15 @@ var _HttpClient = class _HttpClient {
1172
1346
  headers: filteredHeaders,
1173
1347
  body: this.prepareRequestBody(config),
1174
1348
  signal: controller.signal,
1175
- // Only where `window` exists. A server or edge runtime has no cookie jar for the mode to
1176
- // govern, so the field is left off and the runtime's default stands. A Web Worker gets
1177
- // that default too: it has cookies but no `window`.
1178
- ...typeof window !== "undefined" && {
1179
- credentials: credentialsModeFor(this.config.browser.includeCredentials)
1349
+ // Only in a browser — a `window` with a `document` (`isBrowser()`). A server or edge
1350
+ // runtime has no cookie jar for the mode to govern, so the field is left off and the
1351
+ // runtime's default stands; that includes Deno 1.x, whose global is a `window` with no
1352
+ // `document`. A Web Worker gets that default too: it has cookies but no `window`.
1353
+ ...chunkTZL374LS_cjs.isBrowser() && {
1354
+ credentials: credentialsModeFor(this.connection.config.browser.includeCredentials)
1180
1355
  }
1181
1356
  };
1182
- const send = this.config.fetch ?? globalThis.fetch;
1357
+ const send = this.connection.config.fetch ?? globalThis.fetch;
1183
1358
  const response = await send(url, requestInit);
1184
1359
  clearTimeout(timeoutId);
1185
1360
  const responseBody = await this.parseResponse(response);
@@ -1212,10 +1387,8 @@ var _HttpClient = class _HttpClient {
1212
1387
  }
1213
1388
  if (error instanceof DOMException && error.name === "AbortError") {
1214
1389
  if (!timedOut && isAborted(caller)) throw error;
1215
- throw new TimeoutError(
1216
- `Request timed out after ${config.timeout ?? this.timeout}ms`,
1217
- config.timeout ?? this.timeout
1218
- );
1390
+ const timeout = config.timeout ?? this.connection.timeout;
1391
+ throw new TimeoutError(`Request timed out after ${timeout}ms`, timeout);
1219
1392
  }
1220
1393
  if (error instanceof TypeError && error.message.includes("CORS")) {
1221
1394
  const corsError = new NetworkError(
@@ -1229,9 +1402,10 @@ var _HttpClient = class _HttpClient {
1229
1402
  suggestion: "Check if the API server allows requests from your domain and has proper CORS headers configured."
1230
1403
  }
1231
1404
  );
1232
- if (typeof window !== "undefined" && this.config.browser.onCorsError) {
1405
+ const { onCorsError } = this.connection.config.browser;
1406
+ if (chunkTZL374LS_cjs.isBrowser() && onCorsError) {
1233
1407
  try {
1234
- this.config.browser.onCorsError(corsError, {
1408
+ onCorsError(corsError, {
1235
1409
  url: config.url,
1236
1410
  method: config.method,
1237
1411
  originalError: error
@@ -1248,20 +1422,20 @@ var _HttpClient = class _HttpClient {
1248
1422
  }
1249
1423
  }
1250
1424
  /**
1251
- * Filter headers that browsers don't allow to be set manually
1425
+ * The headers without those a browser controls itself, when this is a browser; unchanged when it
1426
+ * is not.
1427
+ *
1428
+ * A name is matched without regard to case (RFC 9110 §5.1), because a browser refuses `Origin`
1429
+ * whatever case a page spells it in. This matched only the all-lowercase and all-uppercase
1430
+ * spellings, so `Origin` and `User-Agent` — how people write them — reached `fetch` (REL-1409).
1252
1431
  */
1253
1432
  filterHeadersForBrowser(headers) {
1254
- if (typeof window !== "undefined") {
1255
- const filteredHeaders = { ...headers };
1256
- const forbiddenHeaders = ["user-agent", "referer", "origin", "host", "connection", "upgrade"];
1257
- forbiddenHeaders.forEach((header) => {
1258
- delete filteredHeaders[header];
1259
- delete filteredHeaders[header.toLowerCase()];
1260
- delete filteredHeaders[header.toUpperCase()];
1261
- });
1262
- return filteredHeaders;
1263
- }
1264
- return headers;
1433
+ if (!chunkTZL374LS_cjs.isBrowser()) return headers;
1434
+ return Object.fromEntries(
1435
+ Object.entries(headers).filter(
1436
+ ([name]) => !BROWSER_CONTROLLED_HEADERS.has(name.toLowerCase())
1437
+ )
1438
+ );
1265
1439
  }
1266
1440
  /**
1267
1441
  * Prepare request body based on content type and environment
@@ -1297,7 +1471,7 @@ var _HttpClient = class _HttpClient {
1297
1471
  if (url.startsWith("http://") || url.startsWith("https://")) {
1298
1472
  return url;
1299
1473
  }
1300
- return `${this.baseUrl}${url.startsWith("/") ? "" : "/"}${url}`;
1474
+ return `${this.connection.baseUrl}${url.startsWith("/") ? "" : "/"}${url}`;
1301
1475
  }
1302
1476
  mergeRequestConfig(config) {
1303
1477
  const url = this.resolveUrl(config.url);
@@ -1306,11 +1480,11 @@ var _HttpClient = class _HttpClient {
1306
1480
  return {
1307
1481
  ...config,
1308
1482
  url: finalUrl,
1309
- headers: {
1310
- ...this.defaultHeaders,
1311
- ...config.headers
1312
- },
1313
- timeout: config.timeout ?? this.timeout
1483
+ // The request's own headers replace the client's of the same name, whatever the case: a
1484
+ // per-request `x-relyio-dataset` sent beside the client's `X-RelyIO-Dataset` is one header
1485
+ // `Headers` joins into `main, staging`.
1486
+ headers: layerHeaders(this.connection.defaultHeaders, config.headers),
1487
+ timeout: config.timeout ?? this.connection.timeout
1314
1488
  };
1315
1489
  }
1316
1490
  buildQueryString(params) {
@@ -1334,9 +1508,10 @@ var _HttpClient = class _HttpClient {
1334
1508
  return obj;
1335
1509
  }
1336
1510
  log(level, message, data) {
1337
- if (!this.debug) return;
1338
- if (this.logger != null) {
1339
- this.logger(level, message, data);
1511
+ const { debug } = this.connection.config;
1512
+ if (!debug.enabled) return;
1513
+ if (debug.logger != null) {
1514
+ debug.logger(level, message, data);
1340
1515
  } else {
1341
1516
  console.log(`[RelyIO SDK ${level.toUpperCase()}] ${message}`, data ?? "");
1342
1517
  }
@@ -1345,29 +1520,104 @@ var _HttpClient = class _HttpClient {
1345
1520
  chunkMWMSFIWN_cjs.__name(_HttpClient, "HttpClient");
1346
1521
  var HttpClient = _HttpClient;
1347
1522
  var API_KEY_HEADER = "X-API-Key";
1523
+ var DATASET_HEADER = "X-RelyIO-Dataset";
1524
+ var BROWSER_CONTROLLED_HEADERS = /* @__PURE__ */ new Set([
1525
+ "user-agent",
1526
+ "referer",
1527
+ "origin",
1528
+ "host",
1529
+ "connection",
1530
+ "upgrade"
1531
+ ]);
1532
+ function connectionFor(config) {
1533
+ const globalWithVersion = globalThis;
1534
+ const defaultHeaders = layerHeaders(
1535
+ layerHeaders({ "Content-Type": "application/json" }, config.headers),
1536
+ {
1537
+ [API_KEY_HEADER]: presentKey(config.apiKey),
1538
+ [DATASET_HEADER]: config.dataset,
1539
+ // Only outside a browser, which controls this header. "A browser" is the core's one
1540
+ // definition, `isBrowser()` — every environment question in this file asks it.
1541
+ "User-Agent": chunkTZL374LS_cjs.isBrowser() ? void 0 : `RelyIO-SDK/${globalWithVersion.__VERSION__ ?? "0.0.0"}`
1542
+ }
1543
+ );
1544
+ return {
1545
+ config,
1546
+ baseUrl: config.baseUrl,
1547
+ defaultHeaders,
1548
+ timeout: config.timeout,
1549
+ // The policy, not a predicate over `(error, attempt)`: whether a request may be sent again is a
1550
+ // property of the **route** as much as of the refusal, and a callback that never sees the
1551
+ // request cannot express that.
1552
+ retryPolicy: new DefaultRetryPolicy({
1553
+ maxAttempts: config.retryAttempts,
1554
+ baseDelay: 1e3,
1555
+ maxDelay: 1e4,
1556
+ backoffFactor: 2,
1557
+ jitter: true
1558
+ })
1559
+ };
1560
+ }
1561
+ chunkMWMSFIWN_cjs.__name(connectionFor, "connectionFor");
1562
+ function layerHeaders(base, over) {
1563
+ const layered = { ...base };
1564
+ for (const [name, value] of Object.entries(over ?? {})) {
1565
+ if (value === void 0) continue;
1566
+ const lower = name.toLowerCase();
1567
+ for (const existing of Object.keys(layered)) {
1568
+ if (existing.toLowerCase() === lower) delete layered[existing];
1569
+ }
1570
+ if (value !== null) layered[name] = value;
1571
+ }
1572
+ return layered;
1573
+ }
1574
+ chunkMWMSFIWN_cjs.__name(layerHeaders, "layerHeaders");
1348
1575
  function presentKey(value) {
1349
1576
  return typeof value === "string" && value.trim() !== "" ? value : void 0;
1350
1577
  }
1351
1578
  chunkMWMSFIWN_cjs.__name(presentKey, "presentKey");
1352
1579
  function credentialsModeFor(includeCredentials) {
1353
- switch (includeCredentials) {
1354
- case false: {
1355
- return "same-origin";
1356
- }
1357
- case true:
1358
- case void 0: {
1359
- return "include";
1360
- }
1361
- }
1580
+ return includeCredentials ? "include" : "same-origin";
1362
1581
  }
1363
1582
  chunkMWMSFIWN_cjs.__name(credentialsModeFor, "credentialsModeFor");
1364
1583
 
1584
+ // src/content/client-locale.ts
1585
+ function callLocale(named, clientLocale) {
1586
+ if (named !== void 0) return named;
1587
+ return clientLocale === void 0 || clientLocale === "" ? void 0 : clientLocale;
1588
+ }
1589
+ chunkMWMSFIWN_cjs.__name(callLocale, "callLocale");
1590
+ function readClientLocale(locale) {
1591
+ return typeof locale === "function" ? locale : () => locale;
1592
+ }
1593
+ chunkMWMSFIWN_cjs.__name(readClientLocale, "readClientLocale");
1594
+
1365
1595
  // src/content/query-builder.ts
1596
+ function namesMetadata(key) {
1597
+ return key === "_metadata" || key.startsWith("_metadata.");
1598
+ }
1599
+ chunkMWMSFIWN_cjs.__name(namesMetadata, "namesMetadata");
1600
+ function refuseMetadataConditions(conditions) {
1601
+ const named = Object.keys(conditions).filter(namesMetadata);
1602
+ if (named.length === 0) return;
1603
+ throw new ValidationError(
1604
+ `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.`,
1605
+ named.map((field) => ({
1606
+ field,
1607
+ code: "METADATA_CONDITION",
1608
+ message: `${field} is not a field the API can filter on`
1609
+ }))
1610
+ );
1611
+ }
1612
+ chunkMWMSFIWN_cjs.__name(refuseMetadataConditions, "refuseMetadataConditions");
1366
1613
  var _ContentQueryBuilder = class _ContentQueryBuilder {
1367
- constructor(httpClient, contentType, defaultLocale) {
1614
+ /**
1615
+ * @param locale - The client's locale (`withLocale()`, `updateConfig({ locale })`), sent when the
1616
+ * query names none: the function `TypedRelyIOClient` hands over, or a locale fixed for it
1617
+ */
1618
+ constructor(httpClient, contentType, locale) {
1368
1619
  this.httpClient = httpClient;
1369
1620
  this.contentType = contentType;
1370
- this.defaultLocale = defaultLocale;
1371
1621
  chunkMWMSFIWN_cjs.__publicField(this, "queryOptions", {});
1372
1622
  chunkMWMSFIWN_cjs.__publicField(this, "localeValue");
1373
1623
  chunkMWMSFIWN_cjs.__publicField(this, "localesArray");
@@ -1377,6 +1627,21 @@ var _ContentQueryBuilder = class _ContentQueryBuilder {
1377
1627
  chunkMWMSFIWN_cjs.__publicField(this, "pageNumber");
1378
1628
  chunkMWMSFIWN_cjs.__publicField(this, "pageSizeValue");
1379
1629
  chunkMWMSFIWN_cjs.__publicField(this, "requestHeaders");
1630
+ /** The client's locale, asked when the query is sent rather than copied when it was built. */
1631
+ chunkMWMSFIWN_cjs.__publicField(this, "clientLocale");
1632
+ chunkMWMSFIWN_cjs.__publicField(this, "freshValue");
1633
+ this.clientLocale = readClientLocale(locale);
1634
+ }
1635
+ /**
1636
+ * The feed position this read follows — a change's `fresh` (`FeedChange`), or a resync's. Sent as
1637
+ * `?_fresh`, the one term the API declares for it: no cache — the Content API's edge or the
1638
+ * browser's own — holds an answer for that position from before it, so the read is answered after
1639
+ * the change, and every read naming the same position shares that answer. The answer is the
1640
+ * read's own; omit it for a read no change prompted.
1641
+ */
1642
+ fresh(position) {
1643
+ this.freshValue = position;
1644
+ return this;
1380
1645
  }
1381
1646
  /**
1382
1647
  * Headers this query adds to the client's own, e.g. `X-RelyIO-Dataset` to list another dataset
@@ -1388,9 +1653,11 @@ var _ContentQueryBuilder = class _ContentQueryBuilder {
1388
1653
  return this;
1389
1654
  }
1390
1655
  /**
1391
- * Add where conditions to the query
1656
+ * Add where conditions to the query: conditions on the fields the content type declares. A
1657
+ * condition on `_metadata` is refused here, before anything is sent (`refuseMetadataConditions`).
1392
1658
  */
1393
1659
  where(conditions) {
1660
+ refuseMetadataConditions(conditions);
1394
1661
  this.queryOptions.where = {
1395
1662
  ...this.queryOptions.where,
1396
1663
  ...conditions
@@ -1647,7 +1914,11 @@ var _ContentQueryBuilder = class _ContentQueryBuilder {
1647
1914
  * Clone the query builder
1648
1915
  */
1649
1916
  clone() {
1650
- const cloned = new _ContentQueryBuilder(this.httpClient, this.contentType, this.defaultLocale);
1917
+ const cloned = new _ContentQueryBuilder(
1918
+ this.httpClient,
1919
+ this.contentType,
1920
+ this.clientLocale
1921
+ );
1651
1922
  cloned.queryOptions = JSON.parse(JSON.stringify(this.queryOptions));
1652
1923
  cloned.localeValue = this.localeValue;
1653
1924
  cloned.localesArray = this.localesArray ? [...this.localesArray] : void 0;
@@ -1656,6 +1927,7 @@ var _ContentQueryBuilder = class _ContentQueryBuilder {
1656
1927
  cloned.pageNumber = this.pageNumber;
1657
1928
  cloned.pageSizeValue = this.pageSizeValue;
1658
1929
  cloned.requestHeaders = this.requestHeaders ? { ...this.requestHeaders } : void 0;
1930
+ cloned.freshValue = this.freshValue;
1659
1931
  return cloned;
1660
1932
  }
1661
1933
  /**
@@ -1722,14 +1994,15 @@ var _ContentQueryBuilder = class _ContentQueryBuilder {
1722
1994
  if (this.queryOptions.cursor !== void 0) {
1723
1995
  params.cursor = this.queryOptions.cursor;
1724
1996
  }
1997
+ const clientLocale = this.clientLocale();
1725
1998
  if (this.localeValue != null && this.localeValue !== "") {
1726
1999
  params.locale = this.localeValue;
1727
2000
  } else if (this.fetchAllLocales) {
1728
2001
  params.locale = "*";
1729
2002
  } else if (this.localesArray && this.localesArray.length > 0) {
1730
2003
  params.locales = this.localesArray.join(",");
1731
- } else if (this.defaultLocale != null && this.defaultLocale !== "") {
1732
- params.locale = this.defaultLocale;
2004
+ } else if (clientLocale != null && clientLocale !== "") {
2005
+ params.locale = clientLocale;
1733
2006
  }
1734
2007
  if (this.pageNumber !== void 0) {
1735
2008
  params.page = this.pageNumber;
@@ -1747,23 +2020,44 @@ var _ContentQueryBuilder = class _ContentQueryBuilder {
1747
2020
  params.childrenLimit = this.childrenOptions.limit;
1748
2021
  }
1749
2022
  }
2023
+ if (this.freshValue !== void 0) {
2024
+ params[FRESH_PARAMETER] = this.freshValue;
2025
+ }
1750
2026
  return params;
1751
2027
  }
1752
2028
  };
1753
2029
  chunkMWMSFIWN_cjs.__name(_ContentQueryBuilder, "ContentQueryBuilder");
1754
2030
  var ContentQueryBuilder = _ContentQueryBuilder;
1755
2031
  var _ContentByIdBuilder = class _ContentByIdBuilder {
1756
- constructor(httpClient, contentType, id, defaultLocale) {
2032
+ /**
2033
+ * @param locale - The client's locale (`withLocale()`, `updateConfig({ locale })`), sent when the
2034
+ * read names none: the function `TypedRelyIOClient` hands over, or a locale fixed for it
2035
+ */
2036
+ constructor(httpClient, contentType, id, locale) {
1757
2037
  this.httpClient = httpClient;
1758
2038
  this.contentType = contentType;
1759
2039
  this.id = id;
1760
- this.defaultLocale = defaultLocale;
1761
2040
  chunkMWMSFIWN_cjs.__publicField(this, "includeRelations");
1762
2041
  chunkMWMSFIWN_cjs.__publicField(this, "localeValue");
1763
2042
  chunkMWMSFIWN_cjs.__publicField(this, "localesArray");
1764
2043
  chunkMWMSFIWN_cjs.__publicField(this, "fetchAllLocales", false);
1765
2044
  chunkMWMSFIWN_cjs.__publicField(this, "childrenOptions");
1766
2045
  chunkMWMSFIWN_cjs.__publicField(this, "requestHeaders");
2046
+ /** The client's locale, asked when the read is sent rather than copied when it was built. */
2047
+ chunkMWMSFIWN_cjs.__publicField(this, "clientLocale");
2048
+ chunkMWMSFIWN_cjs.__publicField(this, "freshValue");
2049
+ this.clientLocale = readClientLocale(locale);
2050
+ }
2051
+ /**
2052
+ * The feed position this read follows — a change's `fresh` (`FeedChange`), or a resync's. Sent as
2053
+ * `?_fresh`, the one term the API declares for it: no cache — the Content API's edge or the
2054
+ * browser's own — holds an answer for that position from before it, so the read is answered after
2055
+ * the change, and every read naming the same position shares that answer. The answer is the
2056
+ * read's own; omit it for a read no change prompted.
2057
+ */
2058
+ fresh(position) {
2059
+ this.freshValue = position;
2060
+ return this;
1767
2061
  }
1768
2062
  /**
1769
2063
  * Include related content in the response
@@ -1833,14 +2127,15 @@ var _ContentByIdBuilder = class _ContentByIdBuilder {
1833
2127
  if (this.includeRelations != null) {
1834
2128
  params.include = this.includeRelations.join(",");
1835
2129
  }
2130
+ const clientLocale = this.clientLocale();
1836
2131
  if (this.localeValue != null && this.localeValue !== "") {
1837
2132
  params.locale = this.localeValue;
1838
2133
  } else if (this.fetchAllLocales) {
1839
2134
  params.locale = "*";
1840
2135
  } else if (this.localesArray && this.localesArray.length > 0) {
1841
2136
  params.locales = this.localesArray.join(",");
1842
- } else if (this.defaultLocale != null && this.defaultLocale !== "") {
1843
- params.locale = this.defaultLocale;
2137
+ } else if (clientLocale != null && clientLocale !== "") {
2138
+ params.locale = clientLocale;
1844
2139
  }
1845
2140
  if (this.childrenOptions != null) {
1846
2141
  if (this.childrenOptions.contentTypes) {
@@ -1852,6 +2147,9 @@ var _ContentByIdBuilder = class _ContentByIdBuilder {
1852
2147
  params.childrenLimit = this.childrenOptions.limit;
1853
2148
  }
1854
2149
  }
2150
+ if (this.freshValue !== void 0) {
2151
+ params[FRESH_PARAMETER] = this.freshValue;
2152
+ }
1855
2153
  try {
1856
2154
  return await this.httpClient.fetchPayload(
1857
2155
  "/v1/{contentType}/{id}",
@@ -1905,22 +2203,28 @@ chunkMWMSFIWN_cjs.__name(entityTagOf, "entityTagOf");
1905
2203
 
1906
2204
  // src/content/content-client.ts
1907
2205
  var _ContentClient = class _ContentClient {
1908
- constructor(httpClient, contentType, defaultLocale) {
2206
+ /**
2207
+ * @param locale - The client's locale: the function `TypedRelyIOClient` hands over, asked on
2208
+ * every call, or a locale fixed for this client
2209
+ */
2210
+ constructor(httpClient, contentType, locale) {
1909
2211
  this.httpClient = httpClient;
1910
2212
  this.contentType = contentType;
1911
- this.defaultLocale = defaultLocale;
2213
+ /** The client's locale, asked on every call rather than copied when this client was taken. */
2214
+ chunkMWMSFIWN_cjs.__publicField(this, "clientLocale");
2215
+ this.clientLocale = readClientLocale(locale);
1912
2216
  }
1913
2217
  /**
1914
2218
  * Create a new query builder for this content type
1915
2219
  */
1916
2220
  query() {
1917
- return new ContentQueryBuilder(this.httpClient, this.contentType, this.defaultLocale);
2221
+ return new ContentQueryBuilder(this.httpClient, this.contentType, this.clientLocale);
1918
2222
  }
1919
2223
  /**
1920
2224
  * Find content by ID with fluent options
1921
2225
  */
1922
2226
  findById(id) {
1923
- return new ContentByIdBuilder(this.httpClient, this.contentType, id, this.defaultLocale);
2227
+ return new ContentByIdBuilder(this.httpClient, this.contentType, id, this.clientLocale);
1924
2228
  }
1925
2229
  /**
1926
2230
  * Fetch all content items
@@ -2004,7 +2308,7 @@ var _ContentClient = class _ContentClient {
2004
2308
  async fetchOneWithTag(id, options) {
2005
2309
  const query = {};
2006
2310
  if (options?.include != null) query.include = options.include.join(",");
2007
- const locale = options?.locale ?? this.defaultLocale;
2311
+ const locale = options?.locale ?? this.clientLocale();
2008
2312
  if (locale != null && locale !== "") query.locale = locale;
2009
2313
  const response = await this.httpClient.send(
2010
2314
  "/v1/{contentType}/{id}",
@@ -2034,16 +2338,17 @@ var _ContentClient = class _ContentClient {
2034
2338
  * @param options - Create options (slug, locale, parentId, sortOrder), and `headers` this one
2035
2339
  * request adds — e.g. `{ 'X-RelyIO-Dataset': 'staging' }` to write a dataset other than the
2036
2340
  * client's. They are sent, never put in the body; `idempotencyKey` still wins over an
2037
- * `Idempotency-Key` among them.
2341
+ * `Idempotency-Key` among them. `locale` defaults to the client's own (`withLocale()`).
2038
2342
  */
2039
2343
  async create(data, options) {
2040
- const { idempotencyKey, headers, ...body } = options ?? {};
2344
+ const { idempotencyKey, headers, locale, ...body } = options ?? {};
2345
+ const sent = callLocale(locale, this.clientLocale());
2041
2346
  return this.httpClient.fetchPayload(
2042
2347
  "/v1/{contentType}",
2043
2348
  "post",
2044
2349
  {
2045
2350
  path: { contentType: this.contentType },
2046
- body: { content: data, ...body },
2351
+ body: { content: data, ...body, ...sent === void 0 ? {} : { locale: sent } },
2047
2352
  headers: requestHeaders(headers, writeHeaders({ idempotencyKey }))
2048
2353
  }
2049
2354
  );
@@ -2054,16 +2359,18 @@ var _ContentClient = class _ContentClient {
2054
2359
  * @param data - Fields to update
2055
2360
  * @param options - Update options (slug, locale, parentId, sortOrder), and `headers` this one
2056
2361
  * request adds, as for {@link create}. `ifMatch` and `idempotencyKey` win over the same
2057
- * header among them.
2362
+ * header among them. `locale` defaults to the client's own, the locale whose entity tag
2363
+ * {@link fetchOneWithTag} reads, so the tag it hands back is checked against the same locale.
2058
2364
  */
2059
2365
  async update(id, data, options) {
2060
- const { idempotencyKey, ifMatch, headers, ...body } = options ?? {};
2366
+ const { idempotencyKey, ifMatch, headers, locale, ...body } = options ?? {};
2367
+ const sent = callLocale(locale, this.clientLocale());
2061
2368
  return this.httpClient.fetchPayload(
2062
2369
  "/v1/{contentType}/{id}",
2063
2370
  "patch",
2064
2371
  {
2065
2372
  path: { contentType: this.contentType, id },
2066
- body: { content: data, ...body },
2373
+ body: { content: data, ...body, ...sent === void 0 ? {} : { locale: sent } },
2067
2374
  headers: requestHeaders(headers, writeHeaders({ idempotencyKey, ifMatch }))
2068
2375
  }
2069
2376
  );
@@ -2095,32 +2402,35 @@ var _ContentClient = class _ContentClient {
2095
2402
  * Get version history for one locale of an entry.
2096
2403
  *
2097
2404
  * Versions are numbered per locale, so this is one locale's timeline: `locale` defaults to the
2098
- * schema default, no fallback is applied, and a locale with no history returns an empty array
2099
- * rather than another locale's rows. Every row names the locale it belongs to.
2405
+ * client's own (`withLocale()`), else the schema default; no fallback is applied, and a locale
2406
+ * with no history returns an empty array rather than another locale's rows. Every row names the
2407
+ * locale it belongs to.
2100
2408
  */
2101
2409
  async versions(id, options) {
2410
+ const locale = callLocale(options?.locale, this.clientLocale());
2102
2411
  return this.httpClient.fetchPayload("/v1/{contentType}/{id}/versions", "get", {
2103
2412
  path: { contentType: this.contentType, id },
2104
2413
  query: {
2105
2414
  limit: options?.limit ?? 10,
2106
- ...options?.locale === void 0 ? {} : { locale: options.locale }
2415
+ ...locale === void 0 ? {} : { locale }
2107
2416
  }
2108
2417
  });
2109
2418
  }
2110
2419
  /**
2111
2420
  * Get a specific version of an entry.
2112
2421
  *
2113
- * `locale` defaults to the schema default. Version numbers are per locale, so version 2 is
2114
- * different content in each one.
2422
+ * `locale` defaults to the client's own (`withLocale()`), else the schema default. Version
2423
+ * numbers are per locale, so version 2 is different content in each one.
2115
2424
  */
2116
2425
  async version(id, versionNumber, options) {
2426
+ const locale = callLocale(options?.locale, this.clientLocale());
2117
2427
  return this.httpClient.fetchPayload("/v1/{contentType}/{id}/versions/{versionNumber}", "get", {
2118
2428
  path: {
2119
2429
  contentType: this.contentType,
2120
2430
  id,
2121
2431
  versionNumber: String(versionNumber)
2122
2432
  },
2123
- query: options?.locale === void 0 ? void 0 : { locale: options.locale }
2433
+ query: locale === void 0 ? void 0 : { locale }
2124
2434
  });
2125
2435
  }
2126
2436
  /**
@@ -2159,7 +2469,7 @@ var _ContentClient = class _ContentClient {
2159
2469
  if (options?.include != null) {
2160
2470
  params.include = options.include.join(",");
2161
2471
  }
2162
- const locale = options?.locale ?? this.defaultLocale;
2472
+ const locale = options?.locale ?? this.clientLocale();
2163
2473
  if (locale != null && locale !== "") {
2164
2474
  params.locale = locale;
2165
2475
  }
@@ -2179,19 +2489,20 @@ var _ContentClient = class _ContentClient {
2179
2489
  * Create a new ContentClient for a different content type
2180
2490
  */
2181
2491
  ofType(contentType) {
2182
- return new _ContentClient(this.httpClient, contentType, this.defaultLocale);
2492
+ return new _ContentClient(this.httpClient, contentType, this.clientLocale);
2183
2493
  }
2184
2494
  /**
2185
- * Create a new ContentClient with a different default locale
2495
+ * Create a new ContentClient with a different default locale, fixed: an update of the client's
2496
+ * locale (`updateConfig({ locale })`) does not reach it
2186
2497
  */
2187
2498
  withLocale(locale) {
2188
2499
  return new _ContentClient(this.httpClient, this.contentType, locale);
2189
2500
  }
2190
2501
  /**
2191
- * Get the current default locale
2502
+ * Get the default locale as it is now: the client's, or the one `withLocale()` fixed
2192
2503
  */
2193
2504
  getDefaultLocale() {
2194
- return this.defaultLocale;
2505
+ return this.clientLocale();
2195
2506
  }
2196
2507
  };
2197
2508
  chunkMWMSFIWN_cjs.__name(_ContentClient, "ContentClient");
@@ -2200,7 +2511,7 @@ var ContentClient = _ContentClient;
2200
2511
  // src/errors/file-errors.ts
2201
2512
  var _FileUploadError = class _FileUploadError extends SDKError {
2202
2513
  constructor(code, message, options = {}) {
2203
- super(message, options.statusCode, options.cause, { fileErrorCode: code });
2514
+ super(message, options.statusCode, options.cause, { ...options.context, fileErrorCode: code });
2204
2515
  chunkMWMSFIWN_cjs.__publicField(this, "code");
2205
2516
  chunkMWMSFIWN_cjs.__publicField(this, "retryable");
2206
2517
  this.code = code;
@@ -2247,11 +2558,52 @@ function validateFile(file, constraints = {}) {
2247
2558
  }
2248
2559
  chunkMWMSFIWN_cjs.__name(validateFile, "validateFile");
2249
2560
 
2561
+ // src/errors/file-error-codes.ts
2562
+ var FILE_ERROR_CODES = [
2563
+ "UPLOAD_TOO_LARGE",
2564
+ "UNSUPPORTED_MIME",
2565
+ "DIMENSION_OUT_OF_RANGE",
2566
+ "ASPECT_RATIO",
2567
+ "CORRUPT_FILE",
2568
+ "UNSUPPORTED_CODEC",
2569
+ "IMAGES_LIMIT_EXCEEDED",
2570
+ "STREAM_ENCODE_FAILED",
2571
+ "SCAN_INFECTED",
2572
+ "INTERNAL"
2573
+ ];
2574
+
2250
2575
  // src/content/asset-client.ts
2576
+ function assertWaitOptions(options) {
2577
+ const { timeoutMs, pollIntervalMs } = options;
2578
+ if (timeoutMs !== void 0 && !(typeof timeoutMs === "number" && timeoutMs >= 0 && timeoutMs <= MAX_TIMER_DELAY_MS)) {
2579
+ throw new Error(
2580
+ `timeoutMs must be a number of milliseconds from 0 to ${MAX_TIMER_DELAY_MS}, and got ${String(timeoutMs)}`
2581
+ );
2582
+ }
2583
+ if (pollIntervalMs !== void 0 && !isTimerDelay(pollIntervalMs)) {
2584
+ throw new Error(
2585
+ `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)}`
2586
+ );
2587
+ }
2588
+ }
2589
+ chunkMWMSFIWN_cjs.__name(assertWaitOptions, "assertWaitOptions");
2251
2590
  function hasText(value) {
2252
2591
  return value != null && value !== "";
2253
2592
  }
2254
2593
  chunkMWMSFIWN_cjs.__name(hasText, "hasText");
2594
+ function processingFailure(subject, stored) {
2595
+ const storedCode = stored?.code;
2596
+ const code = FILE_ERROR_CODES.find((known) => known === storedCode) ?? "PROCESSING_FAILED";
2597
+ return new FileUploadError(
2598
+ code,
2599
+ `Asset ${subject}: ${stored?.message ?? "File processing failed"}`,
2600
+ {
2601
+ retriable: stored?.retriable === true,
2602
+ context: { processingError: stored }
2603
+ }
2604
+ );
2605
+ }
2606
+ chunkMWMSFIWN_cjs.__name(processingFailure, "processingFailure");
2255
2607
  async function computeSha256Hex(blob) {
2256
2608
  try {
2257
2609
  const subtle = globalThis.crypto?.subtle;
@@ -2267,11 +2619,17 @@ async function computeSha256Hex(blob) {
2267
2619
  }
2268
2620
  chunkMWMSFIWN_cjs.__name(computeSha256Hex, "computeSha256Hex");
2269
2621
  var _AssetFileBuilder = class _AssetFileBuilder {
2270
- constructor(httpClient, assetType, entryUid, defaultLocale) {
2622
+ /**
2623
+ * @param locale - The client's locale, sent when a call names none: the function
2624
+ * `TypedRelyIOClient` hands over, or a locale fixed for this builder
2625
+ */
2626
+ constructor(httpClient, assetType, entryUid, locale) {
2271
2627
  this.httpClient = httpClient;
2272
2628
  this.assetType = assetType;
2273
2629
  this.entryUid = entryUid;
2274
- this.defaultLocale = defaultLocale;
2630
+ /** The client's locale, asked on every call rather than copied when this builder was taken. */
2631
+ chunkMWMSFIWN_cjs.__publicField(this, "clientLocale");
2632
+ this.clientLocale = readClientLocale(locale);
2275
2633
  }
2276
2634
  /**
2277
2635
  * The path parameters every `/file/*` route on this builder takes.
@@ -2284,7 +2642,16 @@ var _AssetFileBuilder = class _AssetFileBuilder {
2284
2642
  return { contentType: this.assetType, id: this.entryUid };
2285
2643
  }
2286
2644
  withLocale(locale) {
2287
- return locale ?? this.defaultLocale;
2645
+ return locale ?? this.clientLocale();
2646
+ }
2647
+ /**
2648
+ * The `locale` body field of a multipart call: the session's, else the client's. The API finds
2649
+ * the binding by it, so a session a `withLocale()` client opened must name the same locale on
2650
+ * every later call or its `objectKey` is refused as another binding's.
2651
+ */
2652
+ localeField(locale) {
2653
+ const sent = this.withLocale(locale);
2654
+ return hasText(sent) ? { locale: sent } : {};
2288
2655
  }
2289
2656
  /**
2290
2657
  * Issue a single upload target — an R2 presigned PUT (image/file) or a
@@ -2343,7 +2710,7 @@ var _AssetFileBuilder = class _AssetFileBuilder {
2343
2710
  uploadId: target.uploadId,
2344
2711
  objectKey: target.objectKey,
2345
2712
  partNumber: target.partNumber,
2346
- ...hasText(target.locale) ? { locale: target.locale } : {}
2713
+ ...this.localeField(target.locale)
2347
2714
  }
2348
2715
  });
2349
2716
  }
@@ -2354,7 +2721,7 @@ var _AssetFileBuilder = class _AssetFileBuilder {
2354
2721
  body: {
2355
2722
  uploadId: target.uploadId,
2356
2723
  objectKey: target.objectKey,
2357
- ...hasText(target.locale) ? { locale: target.locale } : {}
2724
+ ...this.localeField(target.locale)
2358
2725
  }
2359
2726
  });
2360
2727
  return payload.parts;
@@ -2367,7 +2734,7 @@ var _AssetFileBuilder = class _AssetFileBuilder {
2367
2734
  uploadId: target.uploadId,
2368
2735
  objectKey: target.objectKey,
2369
2736
  parts: target.parts.map((p) => ({ partNumber: p.partNumber, etag: p.etag })),
2370
- ...hasText(target.locale) ? { locale: target.locale } : {}
2737
+ ...this.localeField(target.locale)
2371
2738
  }
2372
2739
  });
2373
2740
  }
@@ -2378,7 +2745,7 @@ var _AssetFileBuilder = class _AssetFileBuilder {
2378
2745
  body: {
2379
2746
  uploadId: target.uploadId,
2380
2747
  objectKey: target.objectKey,
2381
- ...hasText(target.locale) ? { locale: target.locale } : {}
2748
+ ...this.localeField(target.locale)
2382
2749
  }
2383
2750
  });
2384
2751
  }
@@ -2398,6 +2765,25 @@ var _AssetFileBuilder = class _AssetFileBuilder {
2398
2765
  { path: this.target, query: params }
2399
2766
  );
2400
2767
  }
2768
+ /**
2769
+ * Read where this file's upload got to — `status`, and the processing stage, progress and error
2770
+ * the pipeline recorded — whatever the asset entry is published in.
2771
+ *
2772
+ * An asset is a draft until its file is ready, and stays one when its file fails, so a key that
2773
+ * reads published content finds no entry for an upload in flight or one that failed. This read
2774
+ * answers for the binding itself. It takes the capability the upload calls take, so a read-only
2775
+ * key is refused with `ReadOnlyKeyError`, and it never returns the entry's fields.
2776
+ */
2777
+ async status(options = {}) {
2778
+ const params = {};
2779
+ const locale = this.withLocale(options.locale);
2780
+ if (hasText(locale)) params.locale = locale;
2781
+ return this.httpClient.fetchPayload(
2782
+ "/v1/{contentType}/{id}/file",
2783
+ "get",
2784
+ { path: this.target, query: params }
2785
+ );
2786
+ }
2401
2787
  /**
2402
2788
  * Update the binding's delivery visibility (token-based; no byte relocation —
2403
2789
  * a downgrade purges the gateway cache). Returns the refreshed asset entry.
@@ -2436,14 +2822,31 @@ var _AssetFileBuilder = class _AssetFileBuilder {
2436
2822
  });
2437
2823
  }
2438
2824
  /**
2439
- * Poll the asset entry until its file binding reaches `ready` (resolves with
2440
- * the ready entry) or `failed` (throws). Times out after `timeoutMs`.
2825
+ * Poll the asset entry until its file binding reaches `ready`, and resolve with the ready entry.
2826
+ *
2827
+ * Each poll reads the entry through the key's lens. When that finds nothing — a key that reads
2828
+ * published content, and an asset that is a draft until its file is ready and stays one when it
2829
+ * fails — the poll reads the binding itself ({@link status}), so a failed upload rejects as one
2830
+ * rather than running out the limit (REL-1481).
2831
+ *
2832
+ * @throws {FileUploadError} When processing failed. `code` is the processing code the service
2833
+ * stored — `UNSUPPORTED_CODEC`, `CORRUPT_FILE` and so on — or `PROCESSING_FAILED` where it
2834
+ * stored none this SDK knows; `context.processingError` holds what it stored, and `retryable`
2835
+ * is its `retriable` hint.
2836
+ * @throws {TimeoutError} When `timeoutMs` passes first. The file may still become ready; the
2837
+ * error's `context.timeout` is the limit and `context.fileStatus` the status last seen. A file
2838
+ * that is `ready` in an asset the key's lens never shows — created with `publish: false`, or
2839
+ * awaiting a workflow — ends here too, and the message says so.
2840
+ * @throws {ReadOnlyKeyError} When the key's lens does not show the asset and the key may not read
2841
+ * its binding either: nothing it can read would ever report the file.
2441
2842
  */
2442
2843
  async waitUntilReady(options = {}) {
2844
+ assertWaitOptions(options);
2443
2845
  const timeoutMs = options.timeoutMs ?? 6e4;
2444
2846
  const pollIntervalMs = options.pollIntervalMs ?? 1500;
2445
2847
  const locale = this.withLocale(options.locale);
2446
2848
  const deadline = Date.now() + timeoutMs;
2849
+ const subject = `${this.assetType}/${this.entryUid}`;
2447
2850
  for (; ; ) {
2448
2851
  const builder = new ContentByIdBuilder(
2449
2852
  this.httpClient,
@@ -2453,15 +2856,24 @@ var _AssetFileBuilder = class _AssetFileBuilder {
2453
2856
  );
2454
2857
  if (hasText(locale)) builder.locale(locale);
2455
2858
  const entry = await builder.execute();
2456
- const status = entry?.file?.status;
2457
- if (entry && status === "ready") return entry;
2458
- if (status === "failed") {
2459
- const message = entry?.file?.processing?.error?.message ?? "File processing failed";
2460
- throw new Error(`Asset ${this.assetType}/${this.entryUid}: ${message}`);
2859
+ let status;
2860
+ if (entry === null) {
2861
+ const binding = await this.status({ ...hasText(locale) ? { locale } : {} });
2862
+ status = binding.status;
2863
+ if (status === "failed") throw processingFailure(subject, binding.processing?.error);
2864
+ } else {
2865
+ status = entry.file?.status;
2866
+ if (status === "ready") return entry;
2867
+ if (status === "failed") throw processingFailure(subject, entry.file?.processing?.error);
2461
2868
  }
2462
2869
  if (Date.now() >= deadline) {
2463
- throw new Error(
2464
- `Timed out after ${timeoutMs}ms waiting for ${this.assetType}/${this.entryUid} (status: ${status ?? "unknown"})`
2870
+ 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" : "";
2871
+ throw new TimeoutError(
2872
+ `Timed out after ${timeoutMs}ms waiting for ${subject} (status: ${status ?? "unknown"})${unseen}`,
2873
+ timeoutMs,
2874
+ void 0,
2875
+ void 0,
2876
+ { fileStatus: status }
2465
2877
  );
2466
2878
  }
2467
2879
  await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
@@ -2471,11 +2883,14 @@ var _AssetFileBuilder = class _AssetFileBuilder {
2471
2883
  chunkMWMSFIWN_cjs.__name(_AssetFileBuilder, "AssetFileBuilder");
2472
2884
  var AssetFileBuilder = _AssetFileBuilder;
2473
2885
  var _AssetUploadBuilder = class _AssetUploadBuilder {
2474
- constructor(httpClient, assetType, file, defaultLocale) {
2886
+ /**
2887
+ * @param locale - The client's locale, the upload's when `.locale()` names none: the function
2888
+ * `TypedRelyIOClient` hands over, or a locale fixed for this upload
2889
+ */
2890
+ constructor(httpClient, assetType, file, locale) {
2475
2891
  this.httpClient = httpClient;
2476
2892
  this.assetType = assetType;
2477
2893
  this.file = file;
2478
- this.defaultLocale = defaultLocale;
2479
2894
  chunkMWMSFIWN_cjs.__publicField(this, "_fileName");
2480
2895
  chunkMWMSFIWN_cjs.__publicField(this, "_visibility");
2481
2896
  chunkMWMSFIWN_cjs.__publicField(this, "_locale");
@@ -2487,6 +2902,9 @@ var _AssetUploadBuilder = class _AssetUploadBuilder {
2487
2902
  chunkMWMSFIWN_cjs.__publicField(this, "_onProgress");
2488
2903
  chunkMWMSFIWN_cjs.__publicField(this, "_validate");
2489
2904
  chunkMWMSFIWN_cjs.__publicField(this, "_maxPartRetries", 3);
2905
+ /** The client's locale, read once when the upload starts. */
2906
+ chunkMWMSFIWN_cjs.__publicField(this, "clientLocale");
2907
+ this.clientLocale = readClientLocale(locale);
2490
2908
  }
2491
2909
  /** Subscribe to client → storage upload progress (single PUT + multipart). */
2492
2910
  onProgress(callback) {
@@ -2536,8 +2954,14 @@ var _AssetUploadBuilder = class _AssetUploadBuilder {
2536
2954
  this._slug = slug;
2537
2955
  return this;
2538
2956
  }
2539
- /** Wait until the file binding becomes `ready` before resolving. */
2957
+ /**
2958
+ * Wait until the file binding becomes `ready` before resolving.
2959
+ *
2960
+ * @throws {Error} When `timeoutMs` or `pollIntervalMs` is one the wait cannot honour — refused
2961
+ * here, before anything is uploaded, rather than after the bytes are in.
2962
+ */
2540
2963
  waitForReady(options = true) {
2964
+ if (typeof options === "object") assertWaitOptions(options);
2541
2965
  if (options === false) {
2542
2966
  this._waitForReady = false;
2543
2967
  } else {
@@ -2552,7 +2976,8 @@ var _AssetUploadBuilder = class _AssetUploadBuilder {
2552
2976
  if (!fileName) {
2553
2977
  throw new Error('A file name is required: pass a File, or call .filename("name.ext")');
2554
2978
  }
2555
- const locale = this._locale ?? this.defaultLocale;
2979
+ const clientLocale = this.clientLocale();
2980
+ const locale = this._locale ?? clientLocale;
2556
2981
  if (this._validate) {
2557
2982
  const violations = validateFile(this.file, this._validate);
2558
2983
  if (violations.length > 0) {
@@ -2563,7 +2988,7 @@ var _AssetUploadBuilder = class _AssetUploadBuilder {
2563
2988
  }
2564
2989
  }
2565
2990
  const hash = await computeSha256Hex(this.file);
2566
- const client = new AssetClient(this.httpClient, this.assetType, this.defaultLocale);
2991
+ const client = new AssetClient(this.httpClient, this.assetType, clientLocale);
2567
2992
  const created = await client.create(this._content, {
2568
2993
  ...hasText(this._slug) ? { slug: this._slug } : {},
2569
2994
  ...hasText(locale) ? { locale } : {},
@@ -2714,17 +3139,26 @@ var _AssetUploadBuilder = class _AssetUploadBuilder {
2714
3139
  chunkMWMSFIWN_cjs.__name(_AssetUploadBuilder, "AssetUploadBuilder");
2715
3140
  var AssetUploadBuilder = _AssetUploadBuilder;
2716
3141
  var _AssetClient = class _AssetClient {
2717
- constructor(httpClient, assetType, defaultLocale) {
3142
+ /**
3143
+ * @param locale - The client's locale: the function `TypedRelyIOClient` hands over, asked on
3144
+ * every call, or a locale fixed for this client
3145
+ */
3146
+ constructor(httpClient, assetType, locale) {
2718
3147
  this.httpClient = httpClient;
2719
3148
  this.assetType = assetType;
2720
- this.defaultLocale = defaultLocale;
3149
+ /** The client's locale, asked on every call rather than copied when this client was taken. */
3150
+ chunkMWMSFIWN_cjs.__publicField(this, "clientLocale");
3151
+ this.clientLocale = readClientLocale(locale);
2721
3152
  }
2722
- /** Create a query builder for this asset type. */
3153
+ /**
3154
+ * Create a query builder for this asset type. It resolves asset reads; its conditions name the
3155
+ * asset type's fields, which the `file` and `urls` blocks are not.
3156
+ */
2723
3157
  query() {
2724
3158
  return new ContentQueryBuilder(
2725
3159
  this.httpClient,
2726
3160
  this.assetType,
2727
- this.defaultLocale
3161
+ this.clientLocale
2728
3162
  );
2729
3163
  }
2730
3164
  /** Find an asset entry by id (or slug) with fluent options. */
@@ -2733,7 +3167,7 @@ var _AssetClient = class _AssetClient {
2733
3167
  this.httpClient,
2734
3168
  this.assetType,
2735
3169
  id,
2736
- this.defaultLocale
3170
+ this.clientLocale
2737
3171
  );
2738
3172
  }
2739
3173
  /** Fetch a single asset entry by id (or slug), or `null` if not found. */
@@ -2754,28 +3188,34 @@ var _AssetClient = class _AssetClient {
2754
3188
  * Create a new asset entry as a draft. The binary is uploaded separately via
2755
3189
  * `file(uid)` (or use the high-level `upload()` helper), and the entry
2756
3190
  * auto-publishes when its file becomes ready (unless `publish: false`).
3191
+ * `locale` defaults to the client's own (`withLocale()`).
2757
3192
  */
2758
3193
  async create(data, options) {
2759
- const { idempotencyKey, ...rest } = options ?? {};
3194
+ const { idempotencyKey, locale, ...rest } = options ?? {};
3195
+ const sent = callLocale(locale, this.clientLocale());
2760
3196
  return this.httpClient.fetchPayload(
2761
3197
  "/v1/{contentType}",
2762
3198
  "post",
2763
3199
  {
2764
3200
  path: { contentType: this.assetType },
2765
- body: { content: data, ...rest },
3201
+ body: { content: data, ...rest, ...sent === void 0 ? {} : { locale: sent } },
2766
3202
  headers: writeHeaders({ idempotencyKey })
2767
3203
  }
2768
3204
  );
2769
3205
  }
2770
- /** Update an asset entry's metadata fields (and/or slug). */
3206
+ /**
3207
+ * Update an asset entry's metadata fields (and/or slug). `locale` defaults to the client's own
3208
+ * (`withLocale()`).
3209
+ */
2771
3210
  async update(id, data, options) {
2772
- const { idempotencyKey, ifMatch, ...rest } = options ?? {};
3211
+ const { idempotencyKey, ifMatch, locale, ...rest } = options ?? {};
3212
+ const sent = callLocale(locale, this.clientLocale());
2773
3213
  return this.httpClient.fetchPayload(
2774
3214
  "/v1/{contentType}/{id}",
2775
3215
  "patch",
2776
3216
  {
2777
3217
  path: { contentType: this.assetType, id },
2778
- body: { content: data, ...rest },
3218
+ body: { content: data, ...rest, ...sent === void 0 ? {} : { locale: sent } },
2779
3219
  headers: writeHeaders({ idempotencyKey, ifMatch })
2780
3220
  }
2781
3221
  );
@@ -2789,21 +3229,24 @@ var _AssetClient = class _AssetClient {
2789
3229
  }
2790
3230
  /** Get the file sub-resource builder for a specific asset entry. */
2791
3231
  file(entryUid) {
2792
- return new AssetFileBuilder(this.httpClient, this.assetType, entryUid, this.defaultLocale);
3232
+ return new AssetFileBuilder(this.httpClient, this.assetType, entryUid, this.clientLocale);
2793
3233
  }
2794
3234
  /** Start a high-level upload (create → presign → PUT → confirm). */
2795
3235
  upload(file) {
2796
- return new AssetUploadBuilder(this.httpClient, this.assetType, file, this.defaultLocale);
3236
+ return new AssetUploadBuilder(this.httpClient, this.assetType, file, this.clientLocale);
2797
3237
  }
2798
3238
  /** Get the asset type id. */
2799
3239
  getAssetType() {
2800
3240
  return this.assetType;
2801
3241
  }
2802
- /** Get the current default locale. */
3242
+ /** Get the default locale as it is now: the client's, or the one `withLocale()` fixed. */
2803
3243
  getDefaultLocale() {
2804
- return this.defaultLocale;
3244
+ return this.clientLocale();
2805
3245
  }
2806
- /** Create a new AssetClient for this type with a different default locale. */
3246
+ /**
3247
+ * Create a new AssetClient for this type with a different default locale, fixed: an update of the
3248
+ * client's locale (`updateConfig({ locale })`) does not reach it.
3249
+ */
2807
3250
  withLocale(locale) {
2808
3251
  return new _AssetClient(this.httpClient, this.assetType, locale);
2809
3252
  }
@@ -2819,12 +3262,18 @@ chunkMWMSFIWN_cjs.__name(isAssetEntry, "isAssetEntry");
2819
3262
 
2820
3263
  // src/content/singleton-client.ts
2821
3264
  var _SingletonClient = class _SingletonClient {
2822
- constructor(httpClient, singletonKey, defaultLocale) {
3265
+ /**
3266
+ * @param locale - The client's locale: the function `TypedRelyIOClient` hands over, asked on
3267
+ * every call, or a locale fixed for this client
3268
+ */
3269
+ constructor(httpClient, singletonKey, locale) {
2823
3270
  this.httpClient = httpClient;
2824
3271
  this.singletonKey = singletonKey;
2825
- this.defaultLocale = defaultLocale;
2826
3272
  chunkMWMSFIWN_cjs.__publicField(this, "includeFields");
2827
3273
  chunkMWMSFIWN_cjs.__publicField(this, "localeValue");
3274
+ /** The client's locale, asked on every call rather than copied when this client was taken. */
3275
+ chunkMWMSFIWN_cjs.__publicField(this, "clientLocale");
3276
+ this.clientLocale = readClientLocale(locale);
2828
3277
  }
2829
3278
  /**
2830
3279
  * Include reference fields in the response
@@ -2864,9 +3313,10 @@ var _SingletonClient = class _SingletonClient {
2864
3313
  async fetchWithTag(options) {
2865
3314
  const query = {};
2866
3315
  const includeValue = options?.include ?? this.includeFields;
2867
- const localeValueFinal = options?.locale ?? this.localeValue ?? this.defaultLocale;
3316
+ const localeValueFinal = options?.locale ?? this.localeValue ?? this.clientLocale();
2868
3317
  if (includeValue) query.include = includeValue.join(",");
2869
3318
  if (localeValueFinal != null && localeValueFinal !== "") query.locale = localeValueFinal;
3319
+ if (options?.fresh !== void 0) query[FRESH_PARAMETER] = options.fresh;
2870
3320
  const response = await this.httpClient.send(
2871
3321
  "/v1/singletons/{key}",
2872
3322
  "get",
@@ -2878,11 +3328,12 @@ var _SingletonClient = class _SingletonClient {
2878
3328
  * Update singleton content. The API auto-publishes on PATCH — there is no
2879
3329
  * separate publish route; calling `update()` writes and publishes.
2880
3330
  * @param data - The content fields to update
2881
- * @param locale - Optional locale for localized content (overrides fluent locale)
3331
+ * @param locale - Optional locale for localized content (overrides the fluent locale, which
3332
+ * overrides the client's own)
2882
3333
  * @param options - `If-Match` / `Idempotency-Key`, both declared on this route
2883
3334
  */
2884
3335
  async update(data, locale, options) {
2885
- const finalLocale = locale ?? this.localeValue;
3336
+ const finalLocale = this.localeOf(locale);
2886
3337
  return this.httpClient.fetchPayload(
2887
3338
  "/v1/singletons/{key}",
2888
3339
  "patch",
@@ -2911,15 +3362,17 @@ var _SingletonClient = class _SingletonClient {
2911
3362
  * Get version history for one locale of the singleton.
2912
3363
  *
2913
3364
  * Versions are numbered per locale, so this is one locale's timeline: `locale` defaults to the
2914
- * schema default, no fallback is applied, and a locale with no history returns an empty array
2915
- * rather than another locale's rows. Every row names the locale it belongs to.
3365
+ * fluent `.locale()`, then the client's own (`withLocale()`), then the schema default; no
3366
+ * fallback is applied, and a locale with no history returns an empty array rather than another
3367
+ * locale's rows. Every row names the locale it belongs to.
2916
3368
  */
2917
3369
  async versions(options) {
3370
+ const locale = this.localeOf(options?.locale);
2918
3371
  const payload = await this.httpClient.fetchPayload("/v1/singletons/{key}/versions", "get", {
2919
3372
  path: { key: this.singletonKey },
2920
3373
  query: {
2921
3374
  limit: options?.limit ?? 10,
2922
- ...options?.locale === void 0 ? {} : { locale: options.locale }
3375
+ ...locale === void 0 ? {} : { locale }
2923
3376
  }
2924
3377
  });
2925
3378
  return payload.versions;
@@ -2927,20 +3380,25 @@ var _SingletonClient = class _SingletonClient {
2927
3380
  /**
2928
3381
  * Get a specific version of the singleton.
2929
3382
  *
2930
- * `locale` defaults to the schema default. Version numbers are per locale, so version 2 is
2931
- * different content in each one.
3383
+ * `locale` defaults to the fluent `.locale()`, then the client's own (`withLocale()`), then the
3384
+ * schema default. Version numbers are per locale, so version 2 is different content in each one.
2932
3385
  */
2933
3386
  async version(versionNumber, options) {
3387
+ const locale = this.localeOf(options?.locale);
2934
3388
  try {
2935
3389
  return await this.httpClient.fetchPayload("/v1/singletons/{key}/versions/{versionNumber}", "get", {
2936
3390
  path: { key: this.singletonKey, versionNumber: String(versionNumber) },
2937
- query: options?.locale === void 0 ? void 0 : { locale: options.locale }
3391
+ query: locale === void 0 ? void 0 : { locale }
2938
3392
  });
2939
3393
  } catch (error) {
2940
3394
  if (isResourceNotFound(error)) return null;
2941
3395
  throw error;
2942
3396
  }
2943
3397
  }
3398
+ /** The locale a write or history read sends: the call's, the fluent one, else the client's. */
3399
+ localeOf(named) {
3400
+ return callLocale(named ?? this.localeValue, this.clientLocale());
3401
+ }
2944
3402
  /**
2945
3403
  * Get the singleton key
2946
3404
  */
@@ -2961,12 +3419,18 @@ var SingletonClient = _SingletonClient;
2961
3419
 
2962
3420
  // src/content/settings-client.ts
2963
3421
  var _SettingsClient = class _SettingsClient {
2964
- constructor(httpClient, groupId, defaultLocale) {
3422
+ /**
3423
+ * @param locale - The client's locale: the function `TypedRelyIOClient` hands over, asked on
3424
+ * every call, or a locale fixed for this client
3425
+ */
3426
+ constructor(httpClient, groupId, locale) {
2965
3427
  this.httpClient = httpClient;
2966
3428
  this.groupId = groupId;
2967
- this.defaultLocale = defaultLocale;
2968
3429
  chunkMWMSFIWN_cjs.__publicField(this, "includeFields");
2969
3430
  chunkMWMSFIWN_cjs.__publicField(this, "localeValue");
3431
+ /** The client's locale, asked on every call rather than copied when this client was taken. */
3432
+ chunkMWMSFIWN_cjs.__publicField(this, "clientLocale");
3433
+ this.clientLocale = readClientLocale(locale);
2970
3434
  }
2971
3435
  /**
2972
3436
  * Include reference fields in the response
@@ -2998,12 +3462,20 @@ var _SettingsClient = class _SettingsClient {
2998
3462
  }
2999
3463
  /**
3000
3464
  * Update settings group
3465
+ *
3466
+ * The fields travel as `content` beside an optional `locale`, the body the route validates
3467
+ * (`UpdateSettingsDto`); any other top-level key is refused `REQUEST_INVALID`.
3468
+ *
3001
3469
  * @param settings - The settings fields to update
3002
- * @param locale - Optional locale for localized settings (overrides fluent locale)
3470
+ * @param locale - Optional locale for localized settings (overrides the fluent locale, which
3471
+ * overrides the client's own)
3003
3472
  */
3004
3473
  async update(settings, locale, options) {
3005
- const finalLocale = locale ?? this.localeValue;
3006
- const body = finalLocale != null && finalLocale !== "" ? { ...settings, locale: finalLocale } : settings;
3474
+ const finalLocale = this.localeOf(locale);
3475
+ const body = {
3476
+ content: settings,
3477
+ ...finalLocale != null && finalLocale !== "" ? { locale: finalLocale } : {}
3478
+ };
3007
3479
  return this.httpClient.fetchPayload(
3008
3480
  "/v1/settings/{groupId}",
3009
3481
  "patch",
@@ -3018,7 +3490,7 @@ var _SettingsClient = class _SettingsClient {
3018
3490
  async fetchWithTag(options) {
3019
3491
  const query = {};
3020
3492
  const includeValue = options?.include ?? this.includeFields;
3021
- const localeValueFinal = options?.locale ?? this.localeValue ?? this.defaultLocale;
3493
+ const localeValueFinal = options?.locale ?? this.localeValue ?? this.clientLocale();
3022
3494
  if (includeValue) query.include = includeValue.join(",");
3023
3495
  if (localeValueFinal != null && localeValueFinal !== "") query.locale = localeValueFinal;
3024
3496
  const response = await this.httpClient.send(
@@ -3030,16 +3502,20 @@ var _SettingsClient = class _SettingsClient {
3030
3502
  }
3031
3503
  /**
3032
3504
  * Reset settings to default values
3033
- * @param locale - Optional locale for localized settings
3505
+ *
3506
+ * The route reads its locale from `?locale=` and takes no body, so that is where it is sent.
3507
+ *
3508
+ * @param locale - Optional locale for localized settings (overrides the fluent locale, which
3509
+ * overrides the client's own)
3034
3510
  */
3035
3511
  async reset(locale, options) {
3036
- const finalLocale = locale ?? this.localeValue;
3512
+ const finalLocale = this.localeOf(locale);
3037
3513
  return this.httpClient.fetchPayload(
3038
3514
  "/v1/settings/{groupId}/reset",
3039
3515
  "post",
3040
3516
  {
3041
3517
  path: { groupId: this.groupId },
3042
- body: finalLocale != null && finalLocale !== "" ? { locale: finalLocale } : void 0,
3518
+ query: finalLocale != null && finalLocale !== "" ? { locale: finalLocale } : void 0,
3043
3519
  headers: writeHeaders(options)
3044
3520
  }
3045
3521
  );
@@ -3070,15 +3546,17 @@ var _SettingsClient = class _SettingsClient {
3070
3546
  * Get version history for one locale of the settings group.
3071
3547
  *
3072
3548
  * Versions are numbered per locale, so this is one locale's timeline: `locale` defaults to the
3073
- * schema default, no fallback is applied, and a locale with no history returns an empty array
3074
- * rather than another locale's rows. Every row names the locale it belongs to.
3549
+ * fluent `.locale()`, then the client's own (`withLocale()`), then the schema default; no
3550
+ * fallback is applied, and a locale with no history returns an empty array rather than another
3551
+ * locale's rows. Every row names the locale it belongs to.
3075
3552
  */
3076
3553
  async versions(options) {
3554
+ const locale = this.localeOf(options?.locale);
3077
3555
  const payload = await this.httpClient.fetchPayload("/v1/settings/{groupId}/versions", "get", {
3078
3556
  path: { groupId: this.groupId },
3079
3557
  query: {
3080
3558
  limit: options?.limit ?? 10,
3081
- ...options?.locale === void 0 ? {} : { locale: options.locale }
3559
+ ...locale === void 0 ? {} : { locale }
3082
3560
  }
3083
3561
  });
3084
3562
  return payload.versions;
@@ -3086,20 +3564,25 @@ var _SettingsClient = class _SettingsClient {
3086
3564
  /**
3087
3565
  * Get a specific version of the settings.
3088
3566
  *
3089
- * `locale` defaults to the schema default. Version numbers are per locale, so version 2 is
3090
- * different content in each one.
3567
+ * `locale` defaults to the fluent `.locale()`, then the client's own (`withLocale()`), then the
3568
+ * schema default. Version numbers are per locale, so version 2 is different content in each one.
3091
3569
  */
3092
3570
  async version(versionNumber, options) {
3571
+ const locale = this.localeOf(options?.locale);
3093
3572
  try {
3094
3573
  return await this.httpClient.fetchPayload("/v1/settings/{groupId}/versions/{versionNumber}", "get", {
3095
3574
  path: { groupId: this.groupId, versionNumber: String(versionNumber) },
3096
- query: options?.locale === void 0 ? void 0 : { locale: options.locale }
3575
+ query: locale === void 0 ? void 0 : { locale }
3097
3576
  });
3098
3577
  } catch (error) {
3099
3578
  if (isResourceNotFound(error)) return null;
3100
3579
  throw error;
3101
3580
  }
3102
3581
  }
3582
+ /** The locale a write or history read sends: the call's, the fluent one, else the client's. */
3583
+ localeOf(named) {
3584
+ return callLocale(named ?? this.localeValue, this.clientLocale());
3585
+ }
3103
3586
  /**
3104
3587
  * Get the settings group ID
3105
3588
  */
@@ -3121,15 +3604,19 @@ var SettingsClient = _SettingsClient;
3121
3604
  // src/search/search-client.ts
3122
3605
  var _SearchBuilder = class _SearchBuilder {
3123
3606
  /**
3124
- * @param defaultLocale - The client's locale (`withLocale()`), sent when `.locale()` names none
3607
+ * @param locale - The client's locale (`withLocale()`, `updateConfig({ locale })`), sent when
3608
+ * `.locale()` names none: the function `TypedRelyIOClient` hands over, or a locale fixed for it
3125
3609
  */
3126
- constructor(httpClient, query, defaultLocale) {
3610
+ constructor(httpClient, query, locale) {
3127
3611
  this.httpClient = httpClient;
3128
- this.defaultLocale = defaultLocale;
3129
3612
  chunkMWMSFIWN_cjs.__publicField(this, "query");
3130
3613
  chunkMWMSFIWN_cjs.__publicField(this, "searchOptions", {});
3131
3614
  chunkMWMSFIWN_cjs.__publicField(this, "requestHeaders");
3615
+ chunkMWMSFIWN_cjs.__publicField(this, "freshValue");
3616
+ /** The client's locale, asked when the query is sent rather than copied when it was built. */
3617
+ chunkMWMSFIWN_cjs.__publicField(this, "clientLocale");
3132
3618
  this.query = query;
3619
+ this.clientLocale = readClientLocale(locale);
3133
3620
  }
3134
3621
  /**
3135
3622
  * Headers this search adds to the client's own, e.g. `X-RelyIO-Dataset` to search another
@@ -3139,6 +3626,15 @@ var _SearchBuilder = class _SearchBuilder {
3139
3626
  this.requestHeaders = headers;
3140
3627
  return this;
3141
3628
  }
3629
+ /**
3630
+ * The feed position this search follows — a change's `fresh` (`FeedChange`), or a resync's. Sent
3631
+ * as `?_fresh` on the query string beside the body: the edge keys a search on both, so no answer
3632
+ * it holds from before that position can answer it. The answer is the search's own.
3633
+ */
3634
+ fresh(position) {
3635
+ this.freshValue = position;
3636
+ return this;
3637
+ }
3142
3638
  /**
3143
3639
  * Search within a single content type. Where a schema types the client, the name is one of its
3144
3640
  * content types and the results are that type's entries.
@@ -3160,10 +3656,12 @@ var _SearchBuilder = class _SearchBuilder {
3160
3656
  return this;
3161
3657
  }
3162
3658
  /**
3163
- * Add filter conditions
3659
+ * Add filter conditions, on the fields the content types declare. A condition on `_metadata` is
3660
+ * refused here, before anything is sent.
3164
3661
  * @param conditions - Filter conditions
3165
3662
  */
3166
3663
  where(conditions) {
3664
+ refuseMetadataConditions(conditions);
3167
3665
  this.searchOptions.where = {
3168
3666
  ...this.searchOptions.where,
3169
3667
  ...conditions
@@ -3271,14 +3769,18 @@ var _SearchBuilder = class _SearchBuilder {
3271
3769
  if (this.searchOptions.explain === true) {
3272
3770
  body.explain = true;
3273
3771
  }
3274
- const locale = this.searchOptions.locale ?? this.defaultLocale;
3772
+ const locale = this.searchOptions.locale ?? this.clientLocale();
3275
3773
  if (locale != null && locale !== "") {
3276
3774
  body.locale = locale;
3277
3775
  }
3278
3776
  const response = await this.httpClient.send(
3279
3777
  "/v1/search",
3280
3778
  "post",
3281
- { body, headers: this.requestHeaders }
3779
+ {
3780
+ body,
3781
+ headers: this.requestHeaders,
3782
+ ...this.freshValue === void 0 ? {} : { query: { [FRESH_PARAMETER]: this.freshValue } }
3783
+ }
3282
3784
  );
3283
3785
  return response.body;
3284
3786
  }
@@ -3299,14 +3801,17 @@ chunkMWMSFIWN_cjs.__name(_SearchBuilder, "SearchBuilder");
3299
3801
  var SearchBuilder = _SearchBuilder;
3300
3802
  var _AutocompleteBuilder = class _AutocompleteBuilder {
3301
3803
  /**
3302
- * @param defaultLocale - The client's locale (`withLocale()`), sent when `.locale()` names none
3804
+ * @param locale - The client's locale (`withLocale()`, `updateConfig({ locale })`), sent when
3805
+ * `.locale()` names none: the function `TypedRelyIOClient` hands over, or a locale fixed for it
3303
3806
  */
3304
- constructor(httpClient, query, defaultLocale) {
3807
+ constructor(httpClient, query, locale) {
3305
3808
  this.httpClient = httpClient;
3306
- this.defaultLocale = defaultLocale;
3307
3809
  chunkMWMSFIWN_cjs.__publicField(this, "query");
3308
3810
  chunkMWMSFIWN_cjs.__publicField(this, "autocompleteOptions", {});
3811
+ /** The client's locale, asked when the query is sent rather than copied when it was built. */
3812
+ chunkMWMSFIWN_cjs.__publicField(this, "clientLocale");
3309
3813
  this.query = query;
3814
+ this.clientLocale = readClientLocale(locale);
3310
3815
  }
3311
3816
  /**
3312
3817
  * Autocomplete within a content type
@@ -3332,10 +3837,12 @@ var _AutocompleteBuilder = class _AutocompleteBuilder {
3332
3837
  return this;
3333
3838
  }
3334
3839
  /**
3335
- * Add filter conditions
3840
+ * Add filter conditions, on the fields the content types declare. A condition on `_metadata` is
3841
+ * refused here, before anything is sent.
3336
3842
  * @param conditions - Filter conditions
3337
3843
  */
3338
3844
  where(conditions) {
3845
+ refuseMetadataConditions(conditions);
3339
3846
  this.autocompleteOptions.where = {
3340
3847
  ...this.autocompleteOptions.where,
3341
3848
  ...conditions
@@ -3382,7 +3889,7 @@ var _AutocompleteBuilder = class _AutocompleteBuilder {
3382
3889
  if (this.autocompleteOptions.limit !== void 0) {
3383
3890
  body.limit = this.autocompleteOptions.limit;
3384
3891
  }
3385
- const locale = this.autocompleteOptions.locale ?? this.defaultLocale;
3892
+ const locale = this.autocompleteOptions.locale ?? this.clientLocale();
3386
3893
  if (locale != null && locale !== "") {
3387
3894
  body.locale = locale;
3388
3895
  }
@@ -3400,15 +3907,18 @@ chunkMWMSFIWN_cjs.__name(_AutocompleteBuilder, "AutocompleteBuilder");
3400
3907
  var AutocompleteBuilder = _AutocompleteBuilder;
3401
3908
  var _SimilarBuilder = class _SimilarBuilder {
3402
3909
  /**
3403
- * @param defaultLocale - The client's locale (`withLocale()`), sent when `.locale()` names none
3910
+ * @param locale - The client's locale (`withLocale()`, `updateConfig({ locale })`), sent when
3911
+ * `.locale()` names none: the function `TypedRelyIOClient` hands over, or a locale fixed for it
3404
3912
  */
3405
- constructor(httpClient, sourceId, defaultLocale) {
3913
+ constructor(httpClient, sourceId, locale) {
3406
3914
  this.httpClient = httpClient;
3407
- this.defaultLocale = defaultLocale;
3408
3915
  chunkMWMSFIWN_cjs.__publicField(this, "sourceId");
3409
3916
  chunkMWMSFIWN_cjs.__publicField(this, "similarOptions", {});
3410
3917
  chunkMWMSFIWN_cjs.__publicField(this, "requestHeaders");
3918
+ /** The client's locale, asked when the read is sent rather than copied when it was built. */
3919
+ chunkMWMSFIWN_cjs.__publicField(this, "clientLocale");
3411
3920
  this.sourceId = sourceId;
3921
+ this.clientLocale = readClientLocale(locale);
3412
3922
  }
3413
3923
  /**
3414
3924
  * Headers this read adds to the client's own, e.g. `X-RelyIO-Dataset` to read another dataset
@@ -3436,17 +3946,12 @@ var _SimilarBuilder = class _SimilarBuilder {
3436
3946
  this.similarOptions.contentType = void 0;
3437
3947
  return this;
3438
3948
  }
3439
- /**
3440
- * Add filter conditions
3441
- * @param conditions - Filter conditions
3949
+ /*
3950
+ * No `where()` (REL-1452). Similar ranks from the source entry's stored vector, and the vector
3951
+ * index can pre-filter by content type and locale only — never by an entry field. The API used to
3952
+ * accept `where` here and apply nothing; it now refuses the property, so the builder does not
3953
+ * offer a filter that cannot be honoured. Narrow with `.in()`, `.across()` and `.locale()`.
3442
3954
  */
3443
- where(conditions) {
3444
- this.similarOptions.where = {
3445
- ...this.similarOptions.where,
3446
- ...conditions
3447
- };
3448
- return this;
3449
- }
3450
3955
  /**
3451
3956
  * Limit number of results
3452
3957
  * @param count - Maximum results
@@ -3480,13 +3985,10 @@ var _SimilarBuilder = class _SimilarBuilder {
3480
3985
  if (contentTypes != null) {
3481
3986
  body.contentTypes = contentTypes;
3482
3987
  }
3483
- if (this.similarOptions.where && Object.keys(this.similarOptions.where).length > 0) {
3484
- body.where = this.similarOptions.where;
3485
- }
3486
3988
  if (this.similarOptions.limit !== void 0) {
3487
3989
  body.limit = this.similarOptions.limit;
3488
3990
  }
3489
- const locale = this.similarOptions.locale ?? this.defaultLocale;
3991
+ const locale = this.similarOptions.locale ?? this.clientLocale();
3490
3992
  if (locale != null && locale !== "") {
3491
3993
  body.locale = locale;
3492
3994
  }
@@ -3514,41 +4016,36 @@ chunkMWMSFIWN_cjs.__name(_SimilarBuilder, "SimilarBuilder");
3514
4016
  var SimilarBuilder = _SimilarBuilder;
3515
4017
  var _SearchClient = class _SearchClient {
3516
4018
  /**
3517
- * @param defaultLocale - The client's locale (`withLocale()`): every builder here sends it when
3518
- * the query names none, as `content()`'s reads do
4019
+ * @param locale - The client's locale (`withLocale()`, `updateConfig({ locale })`): every builder
4020
+ * here sends it when the query names none, as `content()`'s reads do — the function
4021
+ * `TypedRelyIOClient` hands over, or a locale fixed for this client
3519
4022
  */
3520
- constructor(httpClient, defaultLocale) {
4023
+ constructor(httpClient, locale) {
3521
4024
  this.httpClient = httpClient;
3522
- this.defaultLocale = defaultLocale;
4025
+ /** The client's locale, handed to every builder here to ask when it sends. */
4026
+ chunkMWMSFIWN_cjs.__publicField(this, "clientLocale");
4027
+ this.clientLocale = readClientLocale(locale);
3523
4028
  }
3524
4029
  /**
3525
4030
  * Start a search query
3526
4031
  * @param query - Search query string
3527
4032
  */
3528
4033
  search(query) {
3529
- return new SearchBuilder(this.httpClient, query, this.defaultLocale);
4034
+ return new SearchBuilder(this.httpClient, query, this.clientLocale);
3530
4035
  }
3531
4036
  /**
3532
4037
  * Start an autocomplete query
3533
4038
  * @param query - Partial query to autocomplete
3534
4039
  */
3535
4040
  autocomplete(query) {
3536
- return new AutocompleteBuilder(this.httpClient, query, this.defaultLocale);
4041
+ return new AutocompleteBuilder(this.httpClient, query, this.clientLocale);
3537
4042
  }
3538
4043
  /**
3539
4044
  * Find content similar to a given entry
3540
4045
  * @param sourceId - Entry ID to find similar content for
3541
4046
  */
3542
4047
  similar(sourceId) {
3543
- return new SimilarBuilder(this.httpClient, sourceId, this.defaultLocale);
3544
- }
3545
- /**
3546
- * Quick search helper - searches all content types
3547
- * @param query - Search query
3548
- * @param limit - Max results (default 20)
3549
- */
3550
- async global(query, limit = 20) {
3551
- return this.search(query).across("*").limit(limit).execute();
4048
+ return new SimilarBuilder(this.httpClient, sourceId, this.clientLocale);
3552
4049
  }
3553
4050
  };
3554
4051
  chunkMWMSFIWN_cjs.__name(_SearchClient, "SearchClient");
@@ -3556,7 +4053,12 @@ var SearchClient = _SearchClient;
3556
4053
 
3557
4054
  // src/batch/batch-client.ts
3558
4055
  var _BatchBuilder = class _BatchBuilder {
3559
- constructor(httpClient) {
4056
+ /**
4057
+ * @param locale - The client's locale (`withLocale()`, `updateConfig({ locale })`), each
4058
+ * operation's when it names none: the function `TypedRelyIOClient` hands over, or a locale fixed
4059
+ * for this batch
4060
+ */
4061
+ constructor(httpClient, locale) {
3560
4062
  this.httpClient = httpClient;
3561
4063
  chunkMWMSFIWN_cjs.__publicField(this, "operations", []);
3562
4064
  chunkMWMSFIWN_cjs.__publicField(this, "defaultOptions", {
@@ -3566,6 +4068,17 @@ var _BatchBuilder = class _BatchBuilder {
3566
4068
  continueOnError: true
3567
4069
  });
3568
4070
  chunkMWMSFIWN_cjs.__publicField(this, "requestHeaders");
4071
+ /** The client's locale, asked as each operation is added rather than copied when this was built. */
4072
+ chunkMWMSFIWN_cjs.__publicField(this, "clientLocale");
4073
+ this.clientLocale = readClientLocale(locale);
4074
+ }
4075
+ /**
4076
+ * An operation's body with its locale: the one it names, else the client's as it is when the
4077
+ * operation is added.
4078
+ */
4079
+ localized(data) {
4080
+ const locale = callLocale(data.locale, this.clientLocale());
4081
+ return locale === void 0 ? data : { ...data, locale };
3569
4082
  }
3570
4083
  // ==========================================================================
3571
4084
  // ENTRY OPERATIONS
@@ -3581,7 +4094,7 @@ var _BatchBuilder = class _BatchBuilder {
3581
4094
  id,
3582
4095
  method: "POST",
3583
4096
  path: `/${contentType}`,
3584
- body: data
4097
+ body: this.localized(data)
3585
4098
  });
3586
4099
  return this;
3587
4100
  }
@@ -3601,7 +4114,7 @@ var _BatchBuilder = class _BatchBuilder {
3601
4114
  id,
3602
4115
  method: "PATCH",
3603
4116
  path: `/${contentType}/${entryId}`,
3604
- body: data
4117
+ body: this.localized(data)
3605
4118
  });
3606
4119
  return this;
3607
4120
  }
@@ -3621,7 +4134,7 @@ var _BatchBuilder = class _BatchBuilder {
3621
4134
  id,
3622
4135
  method: "PUT",
3623
4136
  path: `/${contentType}/${entryId}`,
3624
- body: data
4137
+ body: this.localized(data)
3625
4138
  });
3626
4139
  return this;
3627
4140
  }
@@ -3653,7 +4166,7 @@ var _BatchBuilder = class _BatchBuilder {
3653
4166
  id,
3654
4167
  method: "PATCH",
3655
4168
  path: `/_singletons/${singletonKey}`,
3656
- body: data
4169
+ body: this.localized(data)
3657
4170
  });
3658
4171
  return this;
3659
4172
  }
@@ -3668,7 +4181,7 @@ var _BatchBuilder = class _BatchBuilder {
3668
4181
  id,
3669
4182
  method: "POST",
3670
4183
  path: `/_singletons/${singletonKey}`,
3671
- body: data
4184
+ body: this.localized(data)
3672
4185
  });
3673
4186
  return this;
3674
4187
  }
@@ -3676,7 +4189,8 @@ var _BatchBuilder = class _BatchBuilder {
3676
4189
  // RAW OPERATIONS
3677
4190
  // ==========================================================================
3678
4191
  /**
3679
- * Add a raw operation (for advanced use cases)
4192
+ * Add a raw operation (for advanced use cases). It is sent exactly as written: the client's
4193
+ * locale is not added to it.
3680
4194
  * @param operation - The operation to add
3681
4195
  */
3682
4196
  raw(operation) {
@@ -3802,7 +4316,7 @@ var _BatchBuilder = class _BatchBuilder {
3802
4316
  * Clone the builder
3803
4317
  */
3804
4318
  clone() {
3805
- const cloned = new _BatchBuilder(this.httpClient);
4319
+ const cloned = new _BatchBuilder(this.httpClient, this.clientLocale);
3806
4320
  cloned.operations = [...this.operations];
3807
4321
  cloned.defaultOptions = { ...this.defaultOptions };
3808
4322
  cloned.requestHeaders = this.requestHeaders === void 0 ? void 0 : { ...this.requestHeaders };
@@ -3855,14 +4369,21 @@ var _BatchBuilder = class _BatchBuilder {
3855
4369
  chunkMWMSFIWN_cjs.__name(_BatchBuilder, "BatchBuilder");
3856
4370
  var BatchBuilder = _BatchBuilder;
3857
4371
  var _BatchClient = class _BatchClient {
3858
- constructor(httpClient) {
4372
+ /**
4373
+ * @param locale - The client's locale: the function `TypedRelyIOClient` hands over, or a locale
4374
+ * fixed for this client
4375
+ */
4376
+ constructor(httpClient, locale) {
3859
4377
  this.httpClient = httpClient;
4378
+ /** The client's locale, handed to every batch this builds to ask as operations are added. */
4379
+ chunkMWMSFIWN_cjs.__publicField(this, "clientLocale");
4380
+ this.clientLocale = readClientLocale(locale);
3860
4381
  }
3861
4382
  /**
3862
- * Create a new batch builder
4383
+ * Create a new batch builder, whose operations default to this client's locale
3863
4384
  */
3864
4385
  create() {
3865
- return new BatchBuilder(this.httpClient);
4386
+ return new BatchBuilder(this.httpClient, this.clientLocale);
3866
4387
  }
3867
4388
  /**
3868
4389
  * Quick bulk create entries
@@ -3913,15 +4434,18 @@ function withClientLocale(parameters, clientLocale) {
3913
4434
  chunkMWMSFIWN_cjs.__name(withClientLocale, "withClientLocale");
3914
4435
  var _AdhocQueryBuilder = class _AdhocQueryBuilder {
3915
4436
  /**
3916
- * @param defaultLocale - The client's locale (`withLocale()`), sent as the `locale` parameter
3917
- * when `.param('locale', …)` binds none
4437
+ * @param locale - The client's locale (`withLocale()`, `updateConfig({ locale })`), sent as the
4438
+ * `locale` parameter when `.param('locale', …)` binds none: the function `TypedRelyIOClient`
4439
+ * hands over, or a locale fixed for this builder
3918
4440
  */
3919
- constructor(httpClient, defaultLocale) {
4441
+ constructor(httpClient, locale) {
3920
4442
  this.httpClient = httpClient;
3921
- this.defaultLocale = defaultLocale;
3922
4443
  chunkMWMSFIWN_cjs.__publicField(this, "queryDef", {});
3923
4444
  chunkMWMSFIWN_cjs.__publicField(this, "parameters", {});
3924
4445
  chunkMWMSFIWN_cjs.__publicField(this, "options", {});
4446
+ /** The client's locale, asked when the query is sent rather than copied when it was built. */
4447
+ chunkMWMSFIWN_cjs.__publicField(this, "clientLocale");
4448
+ this.clientLocale = readClientLocale(locale);
3925
4449
  }
3926
4450
  // ==========================================================================
3927
4451
  // QUERY BLOCKS
@@ -3966,14 +4490,6 @@ var _AdhocQueryBuilder = class _AdhocQueryBuilder {
3966
4490
  this.queryDef.search = config;
3967
4491
  return this;
3968
4492
  }
3969
- /**
3970
- * Add similar block
3971
- * @param config - Similar configuration
3972
- */
3973
- similar(config) {
3974
- this.queryDef.similar = config;
3975
- return this;
3976
- }
3977
4493
  // ==========================================================================
3978
4494
  // FETCH MODIFIERS
3979
4495
  // ==========================================================================
@@ -4150,15 +4666,15 @@ var _AdhocQueryBuilder = class _AdhocQueryBuilder {
4150
4666
  * Execute the query
4151
4667
  */
4152
4668
  async execute() {
4153
- if (!this.queryDef.fetch && !this.queryDef.aggregate && !this.queryDef.search && !this.queryDef.similar) {
4669
+ if (!this.queryDef.fetch && !this.queryDef.aggregate && !this.queryDef.search) {
4154
4670
  throw new Error(
4155
- "Query must have at least one of: fetch, aggregate, search, or similar. Use .from(), .aggregate(), .search(), or .similar()"
4671
+ "Query must have at least one of: fetch, aggregate or search. Use .from(), .aggregate() or .search()"
4156
4672
  );
4157
4673
  }
4158
4674
  const body = {
4159
4675
  query: this.queryDef
4160
4676
  };
4161
- const parameters = withClientLocale(this.parameters, this.defaultLocale);
4677
+ const parameters = withClientLocale(this.parameters, this.clientLocale());
4162
4678
  if (parameters !== void 0 && Object.keys(parameters).length > 0) {
4163
4679
  body.parameters = parameters;
4164
4680
  }
@@ -4191,7 +4707,7 @@ var _AdhocQueryBuilder = class _AdhocQueryBuilder {
4191
4707
  * Clone the builder
4192
4708
  */
4193
4709
  clone() {
4194
- const cloned = new _AdhocQueryBuilder(this.httpClient, this.defaultLocale);
4710
+ const cloned = new _AdhocQueryBuilder(this.httpClient, this.clientLocale);
4195
4711
  cloned.queryDef = JSON.parse(JSON.stringify(this.queryDef));
4196
4712
  cloned.parameters = { ...this.parameters };
4197
4713
  cloned.options = { ...this.options };
@@ -4202,18 +4718,21 @@ chunkMWMSFIWN_cjs.__name(_AdhocQueryBuilder, "AdhocQueryBuilder");
4202
4718
  var AdhocQueryBuilder = _AdhocQueryBuilder;
4203
4719
  var _AdhocQueryClient = class _AdhocQueryClient {
4204
4720
  /**
4205
- * @param defaultLocale - The client's locale (`withLocale()`), sent as the `locale` parameter of
4206
- * every query that binds none
4721
+ * @param locale - The client's locale (`withLocale()`, `updateConfig({ locale })`), sent as the
4722
+ * `locale` parameter of every query that binds none: the function `TypedRelyIOClient` hands
4723
+ * over, or a locale fixed for this client
4207
4724
  */
4208
- constructor(httpClient, defaultLocale) {
4725
+ constructor(httpClient, locale) {
4209
4726
  this.httpClient = httpClient;
4210
- this.defaultLocale = defaultLocale;
4727
+ /** The client's locale, asked on every query rather than copied when this client was taken. */
4728
+ chunkMWMSFIWN_cjs.__publicField(this, "clientLocale");
4729
+ this.clientLocale = readClientLocale(locale);
4211
4730
  }
4212
4731
  /**
4213
4732
  * Create a new query builder
4214
4733
  */
4215
4734
  build() {
4216
- return new AdhocQueryBuilder(this.httpClient, this.defaultLocale);
4735
+ return new AdhocQueryBuilder(this.httpClient, this.clientLocale);
4217
4736
  }
4218
4737
  /**
4219
4738
  * Execute a query definition directly
@@ -4228,7 +4747,7 @@ var _AdhocQueryClient = class _AdhocQueryClient {
4228
4747
  const body = {
4229
4748
  query: definition
4230
4749
  };
4231
- const sent = withClientLocale(parameters, this.defaultLocale);
4750
+ const sent = withClientLocale(parameters, this.clientLocale());
4232
4751
  if (sent !== void 0 && Object.keys(sent).length > 0) {
4233
4752
  body.parameters = sent;
4234
4753
  }
@@ -4277,23 +4796,67 @@ var AdhocQueryClient = _AdhocQueryClient;
4277
4796
 
4278
4797
  // src/changes/changes.ts
4279
4798
  var CHANGES_POLL_TIMEOUT_MS = 25e3 + 5e3;
4799
+ var EXPIRED_REASONS = {
4800
+ retention: "retention",
4801
+ "dataset-replaced": "dataset-replaced",
4802
+ "ahead-of-head": "ahead-of-head",
4803
+ "foreign-cursor": "foreign-cursor",
4804
+ "too-far-behind": "too-far-behind"
4805
+ };
4280
4806
  var _ChangesExpiredError = class _ChangesExpiredError extends ResourceExpiredError {
4281
- /** The base class's signature, so `withContext` can rebuild one. */
4282
- constructor(message, statusCode = 410, cause, context) {
4807
+ /** The base class's signature, so `withContext` can rebuild one; then the position resynced to. */
4808
+ constructor(message, statusCode = 410, cause, context, fresh) {
4283
4809
  super(message, statusCode, cause, context);
4284
4810
  chunkMWMSFIWN_cjs.__publicField(this, "reason");
4811
+ /**
4812
+ * The position the feed resynced to, which a refetch of everything names as `?_fresh`, as a
4813
+ * change's {@link FeedChange.fresh} is named: the head a `/v1/changes` resync took, or the cursor
4814
+ * the edge's `feed.resync` sent. `undefined` when there was no resync to take one — the error was
4815
+ * thrown (`onExpired: 'throw'`) — and on an error a caller built itself.
4816
+ */
4817
+ chunkMWMSFIWN_cjs.__publicField(this, "fresh");
4285
4818
  this.name = "ChangesExpiredError";
4286
4819
  this.reason = expiredReasonOf(message);
4820
+ this.fresh = fresh;
4821
+ }
4822
+ /** The base class rebuilds from its four arguments, which would drop the position resynced to. */
4823
+ withContext(context) {
4824
+ const Constructor = this.constructor;
4825
+ return new Constructor(
4826
+ this.message,
4827
+ this.statusCode,
4828
+ this.cause,
4829
+ { ...this.context, ...context },
4830
+ this.fresh
4831
+ );
4832
+ }
4833
+ /** `/v1/changes`' refusal, with the head the feed resynced to when it did. */
4834
+ static of(expired, fresh) {
4835
+ return new _ChangesExpiredError(
4836
+ expired.message,
4837
+ expired.statusCode,
4838
+ expired,
4839
+ expired.context,
4840
+ fresh
4841
+ );
4287
4842
  }
4288
- static of(expired) {
4289
- return new _ChangesExpiredError(expired.message, expired.statusCode, expired, expired.context);
4843
+ /** The live edge's `feed.resync`: refetch everything, then continue from the cursor it sent. */
4844
+ static resync(reason, cursor) {
4845
+ return new _ChangesExpiredError(
4846
+ `The live edge cannot list the changes since this cursor (${reason}). Refetch everything derived from the feed; it continues from the position it sent.`,
4847
+ 410,
4848
+ void 0,
4849
+ { transport: "live-edge" },
4850
+ cursor
4851
+ );
4290
4852
  }
4291
4853
  };
4292
4854
  chunkMWMSFIWN_cjs.__name(_ChangesExpiredError, "ChangesExpiredError");
4293
4855
  var ChangesExpiredError = _ChangesExpiredError;
4294
4856
  function expiredReasonOf(message) {
4295
- if (message.includes("(retention)")) return "retention";
4296
- if (message.includes("(dataset-replaced)")) return "dataset-replaced";
4857
+ for (const reason of Object.values(EXPIRED_REASONS)) {
4858
+ if (message.includes(`(${reason})`)) return reason;
4859
+ }
4297
4860
  return "unknown";
4298
4861
  }
4299
4862
  chunkMWMSFIWN_cjs.__name(expiredReasonOf, "expiredReasonOf");
@@ -4313,7 +4876,7 @@ async function* pollChanges(http, options = {}) {
4313
4876
  const onExpired = options.onExpired ?? "throw";
4314
4877
  if (onExpired === "throw") throw expired;
4315
4878
  since = void 0;
4316
- resynced = typeof onExpired === "function" ? expired : void 0;
4879
+ resynced = typeof onExpired === "function" ? error : void 0;
4317
4880
  continue;
4318
4881
  }
4319
4882
  const { data, page } = response.body;
@@ -4322,13 +4885,13 @@ async function* pollChanges(http, options = {}) {
4322
4885
  }
4323
4886
  since = page.nextCursor;
4324
4887
  if (resynced !== void 0 && typeof options.onExpired === "function") {
4325
- const expired = resynced;
4888
+ const expired = ChangesExpiredError.of(resynced, page.nextCursor);
4326
4889
  resynced = void 0;
4327
4890
  await options.onExpired(expired);
4328
4891
  if (isAborted(signal)) return;
4329
4892
  }
4330
4893
  for (const change of data) {
4331
- yield change;
4894
+ yield { ...change, fresh: change.cursor };
4332
4895
  if (isAborted(signal)) return;
4333
4896
  }
4334
4897
  if (data.length === 0) {
@@ -4367,6 +4930,477 @@ async function pollOnce(http, since, options) {
4367
4930
  }
4368
4931
  chunkMWMSFIWN_cjs.__name(pollOnce, "pollOnce");
4369
4932
 
4933
+ // src/config/key-kind.ts
4934
+ var PUBLISHABLE_KEY_PREFIX = "rly_pub_";
4935
+ var isPublishableKey = /* @__PURE__ */ chunkMWMSFIWN_cjs.__name((apiKey) => apiKey?.startsWith(PUBLISHABLE_KEY_PREFIX) === true, "isPublishableKey");
4936
+
4937
+ // src/errors/live-errors.ts
4938
+ var _LiveEdgeError = class _LiveEdgeError extends SDKError {
4939
+ constructor(message, end, retry, options = {}) {
4940
+ super(message, options.statusCode, options.cause, options.context);
4941
+ chunkMWMSFIWN_cjs.__publicField(this, "code", "LIVE_EDGE_ENDED");
4942
+ chunkMWMSFIWN_cjs.__publicField(this, "retryable");
4943
+ chunkMWMSFIWN_cjs.__publicField(this, "end");
4944
+ chunkMWMSFIWN_cjs.__publicField(this, "retry");
4945
+ /** A refused poll's `Retry-After`, in milliseconds, where it carried a readable one. */
4946
+ chunkMWMSFIWN_cjs.__publicField(this, "retryAfterMs");
4947
+ this.end = end;
4948
+ this.retry = retry;
4949
+ this.retryable = retry !== "stop";
4950
+ this.retryAfterMs = options.retryAfterMs;
4951
+ }
4952
+ /**
4953
+ * The base class rebuilds an error through its own four-argument constructor, which this class
4954
+ * does not have; rebuilt that way, `end` would be a string and `retry` a number.
4955
+ */
4956
+ withContext(context) {
4957
+ const rebuilt = new _LiveEdgeError(this.message, this.end, this.retry, {
4958
+ ...this.statusCode === void 0 ? {} : { statusCode: this.statusCode },
4959
+ ...this.cause === void 0 ? {} : { cause: this.cause },
4960
+ ...this.retryAfterMs === void 0 ? {} : { retryAfterMs: this.retryAfterMs },
4961
+ context: { ...this.context, ...context }
4962
+ });
4963
+ return rebuilt;
4964
+ }
4965
+ getUserMessage() {
4966
+ return this.message;
4967
+ }
4968
+ };
4969
+ chunkMWMSFIWN_cjs.__name(_LiveEdgeError, "LiveEdgeError");
4970
+ var LiveEdgeError = _LiveEdgeError;
4971
+
4972
+ // src/config/live-url.ts
4973
+ var LIVE_EDGE_OF_API = {
4974
+ "https://api.relyio.com": "https://live.relyio.com"
4975
+ };
4976
+ var HTTP_SCHEME_OF = {
4977
+ "http:": "http:",
4978
+ "https:": "https:",
4979
+ "ws:": "http:",
4980
+ "wss:": "https:"
4981
+ };
4982
+ function liveEdgeAddress(config) {
4983
+ if (config.liveUrl !== void 0) return readLiveUrl(config.liveUrl);
4984
+ const derived = originOf(config.baseUrl);
4985
+ const edge = derived === void 0 ? void 0 : LIVE_EDGE_OF_API[derived];
4986
+ if (edge !== void 0) return { ok: true, url: new URL(edge) };
4987
+ return {
4988
+ ok: false,
4989
+ message: `Live updates need the address of RelyIO\u2019s live edge, and this client cannot derive it: \`baseUrl\` is not RelyIO\u2019s API (${Object.keys(LIVE_EDGE_OF_API).join(", ")}). Set \`liveUrl\` in createClient() to the live edge this API belongs with. Nothing was sent.`
4990
+ };
4991
+ }
4992
+ chunkMWMSFIWN_cjs.__name(liveEdgeAddress, "liveEdgeAddress");
4993
+ function readLiveUrl(liveUrl) {
4994
+ let url;
4995
+ try {
4996
+ url = new URL(liveUrl);
4997
+ } catch {
4998
+ return { ok: false, message: `\`liveUrl\` is not a URL: "${liveUrl}". Nothing was sent.` };
4999
+ }
5000
+ const scheme = HTTP_SCHEME_OF[url.protocol];
5001
+ if (scheme === void 0) {
5002
+ return {
5003
+ ok: false,
5004
+ message: `\`liveUrl\` must be an https:, wss:, http: or ws: URL, not ${url.protocol}. Nothing was sent.`
5005
+ };
5006
+ }
5007
+ url.protocol = scheme;
5008
+ url.search = "";
5009
+ url.hash = "";
5010
+ return { ok: true, url };
5011
+ }
5012
+ chunkMWMSFIWN_cjs.__name(readLiveUrl, "readLiveUrl");
5013
+ function originOf(baseUrl) {
5014
+ try {
5015
+ return new URL(baseUrl).origin;
5016
+ } catch {
5017
+ return void 0;
5018
+ }
5019
+ }
5020
+ chunkMWMSFIWN_cjs.__name(originOf, "originOf");
5021
+
5022
+ // src/changes/live-edge.ts
5023
+ var LIVE_KEEPALIVE_INTERVAL_MS = 3e4;
5024
+ var LIVE_POLLS_BEFORE_SOCKET = 4;
5025
+ async function* subscribeLiveEdge(client, options = {}) {
5026
+ const { signal } = options;
5027
+ if (isAborted(signal)) return;
5028
+ const key = client.apiKey;
5029
+ if (key === void 0 || !isPublishableKey(key)) {
5030
+ throw new LiveEdgeError(NEEDS_PUBLISHABLE_KEY, { kind: "needs-publishable-key" }, "stop");
5031
+ }
5032
+ const address = liveEdgeAddress(client);
5033
+ if (!address.ok) throw new LiveEdgeError(address.message, { kind: "needs-live-url" }, "stop");
5034
+ const request = {
5035
+ base: address.url,
5036
+ key,
5037
+ dataset: client.dataset,
5038
+ types: options.types,
5039
+ ids: options.ids
5040
+ };
5041
+ const reach = { position: options.since };
5042
+ for (; ; ) {
5043
+ const socket = openSocket(client, edgeUrl(request, reach.position, "socket"));
5044
+ if (socket !== void 0) {
5045
+ const ended = yield* followSocket(socket, reach, options);
5046
+ if (ended === "aborted") return;
5047
+ }
5048
+ const polled = yield* followPolls(client, request, reach, options);
5049
+ if (polled === "aborted") return;
5050
+ }
5051
+ }
5052
+ chunkMWMSFIWN_cjs.__name(subscribeLiveEdge, "subscribeLiveEdge");
5053
+ var SOCKET_CONNECTING = 0;
5054
+ var SOCKET_OPEN = 1;
5055
+ function runtimeWebSocket() {
5056
+ const candidate = globalThis.WebSocket;
5057
+ return typeof candidate === "function" ? candidate : void 0;
5058
+ }
5059
+ chunkMWMSFIWN_cjs.__name(runtimeWebSocket, "runtimeWebSocket");
5060
+ function openSocket(client, url) {
5061
+ const Socket = client.webSocket ?? runtimeWebSocket();
5062
+ if (Socket === void 0) return void 0;
5063
+ try {
5064
+ return new Socket(url);
5065
+ } catch {
5066
+ return void 0;
5067
+ }
5068
+ }
5069
+ chunkMWMSFIWN_cjs.__name(openSocket, "openSocket");
5070
+ async function* followSocket(socket, reach, options) {
5071
+ const { signal } = options;
5072
+ const inbox = socketInbox(socket, signal);
5073
+ let open = false;
5074
+ try {
5075
+ for (; ; ) {
5076
+ const event = await inbox.next();
5077
+ if (event === void 0) return "aborted";
5078
+ switch (event.kind) {
5079
+ case "open": {
5080
+ open = true;
5081
+ inbox.keepAlive();
5082
+ continue;
5083
+ }
5084
+ case "error": {
5085
+ if (!open) return "unopened";
5086
+ continue;
5087
+ }
5088
+ case "close": {
5089
+ if (!open) return "unopened";
5090
+ throw closedBy(event.code, event.reason);
5091
+ }
5092
+ case "silent": {
5093
+ throw new LiveEdgeError(
5094
+ "The live edge\u2019s socket stopped answering its keepalive, so the connection is presumed dead; reconnecting from the last cursor.",
5095
+ { kind: "lost" },
5096
+ "retry"
5097
+ );
5098
+ }
5099
+ case "message": {
5100
+ if (event.data === VISITOR_KEEPALIVE.response) continue;
5101
+ const frame = typeof event.data === "string" ? parseVisitorFrame(event.data) : null;
5102
+ if (frame === null)
5103
+ throw unreadable(`a frame the wire does not declare: ${sample(event.data)}`);
5104
+ switch (frame.type) {
5105
+ case VISITOR_FRAMES.opened: {
5106
+ reach.position = frame.cursor;
5107
+ options.opened?.();
5108
+ options.reached?.(frame.cursor);
5109
+ continue;
5110
+ }
5111
+ case VISITOR_FRAMES.changes: {
5112
+ const prior = reach.position;
5113
+ if (prior === void 0) throw unreadable("changes before the socket had a position");
5114
+ for (const change of changesOf(frame.changes, frame.cursor, prior)) {
5115
+ yield change;
5116
+ if (isAborted(signal)) return "aborted";
5117
+ }
5118
+ reach.position = frame.cursor;
5119
+ if (frame.changes.length === 0) options.reached?.(frame.cursor);
5120
+ continue;
5121
+ }
5122
+ case VISITOR_FRAMES.resync: {
5123
+ await expire(frame.reason, frame.cursor, options);
5124
+ if (isAborted(signal)) return "aborted";
5125
+ reach.position = frame.cursor;
5126
+ options.reached?.(frame.cursor);
5127
+ continue;
5128
+ }
5129
+ }
5130
+ }
5131
+ }
5132
+ }
5133
+ } finally {
5134
+ inbox.dispose();
5135
+ }
5136
+ }
5137
+ chunkMWMSFIWN_cjs.__name(followSocket, "followSocket");
5138
+ function socketInbox(socket, signal) {
5139
+ const events = [];
5140
+ let wake;
5141
+ let disposed = false;
5142
+ let heard = true;
5143
+ let keepalive;
5144
+ const rouse = /* @__PURE__ */ chunkMWMSFIWN_cjs.__name(() => {
5145
+ const waking = wake;
5146
+ wake = void 0;
5147
+ waking?.();
5148
+ }, "rouse");
5149
+ const push = /* @__PURE__ */ chunkMWMSFIWN_cjs.__name((event) => {
5150
+ if (disposed) return;
5151
+ events.push(event);
5152
+ rouse();
5153
+ }, "push");
5154
+ socket.addEventListener("open", () => push({ kind: "open" }));
5155
+ socket.addEventListener("message", (event) => {
5156
+ heard = true;
5157
+ push({ kind: "message", data: isRecord(event) ? event["data"] : void 0 });
5158
+ });
5159
+ socket.addEventListener("close", (event) => {
5160
+ const code = isRecord(event) ? event["code"] : void 0;
5161
+ const reason = isRecord(event) ? event["reason"] : void 0;
5162
+ push({
5163
+ kind: "close",
5164
+ // A close event always carries both; a socket that somehow raised one without them closed
5165
+ // abnormally, which is what 1006 says.
5166
+ code: typeof code === "number" ? code : 1006,
5167
+ reason: typeof reason === "string" ? reason : ""
5168
+ });
5169
+ });
5170
+ socket.addEventListener("error", () => push({ kind: "error" }));
5171
+ signal?.addEventListener("abort", rouse, { once: true });
5172
+ return {
5173
+ async next() {
5174
+ for (; ; ) {
5175
+ if (isAborted(signal)) return void 0;
5176
+ const event = events.shift();
5177
+ if (event !== void 0) return event;
5178
+ await new Promise((resolve) => {
5179
+ wake = resolve;
5180
+ });
5181
+ }
5182
+ },
5183
+ keepAlive() {
5184
+ keepalive = setInterval(() => {
5185
+ if (!heard) {
5186
+ push({ kind: "silent" });
5187
+ return;
5188
+ }
5189
+ heard = false;
5190
+ if (socket.readyState === SOCKET_OPEN) socket.send(VISITOR_KEEPALIVE.request);
5191
+ }, LIVE_KEEPALIVE_INTERVAL_MS);
5192
+ },
5193
+ dispose() {
5194
+ disposed = true;
5195
+ if (keepalive !== void 0) clearInterval(keepalive);
5196
+ signal?.removeEventListener("abort", rouse);
5197
+ if (socket.readyState === SOCKET_CONNECTING || socket.readyState === SOCKET_OPEN) {
5198
+ socket.close(1e3);
5199
+ }
5200
+ }
5201
+ };
5202
+ }
5203
+ chunkMWMSFIWN_cjs.__name(socketInbox, "socketInbox");
5204
+ function closedBy(code, reason) {
5205
+ const retry = closeRetryOf(code);
5206
+ const meaning = hasOwn(CLOSE_MEANING, code) ? CLOSE_MEANING[code] : code < 4e3 ? "the connection dropped" : "a refusal this version of the SDK does not know";
5207
+ const said = `close ${code}${reason === "" ? "" : ` ${reason}`}`;
5208
+ return new LiveEdgeError(
5209
+ `The live edge ended this subscription (${said}): ${meaning}. ${AFTER_A_CLOSE[retry]}`,
5210
+ { kind: "closed", code, reason },
5211
+ retry
5212
+ );
5213
+ }
5214
+ chunkMWMSFIWN_cjs.__name(closedBy, "closedBy");
5215
+ var AFTER_A_CLOSE = {
5216
+ stop: "The same request would be refused again, so it was not retried.",
5217
+ retry: "Reconnecting from the last cursor after a short back-off.",
5218
+ "back-off": "Retrying after a back-off of at least a minute, without polling, which the same admission refuses."
5219
+ };
5220
+ function closeRetryOf(code) {
5221
+ if (hasOwn(VISITOR_CLOSE_RETRY, code)) return VISITOR_CLOSE_RETRY[code];
5222
+ return code < 4e3 ? "retry" : "stop";
5223
+ }
5224
+ chunkMWMSFIWN_cjs.__name(closeRetryOf, "closeRetryOf");
5225
+ var CLOSE_MEANING = {
5226
+ [VISITOR_CLOSE_CODES.requestInvalid]: "the edge could not read the request \u2014 a malformed cursor or filter",
5227
+ [VISITOR_CLOSE_CODES.keyRefused]: "the key is not a publishable key the edge accepts \u2014 unknown, revoked or expired",
5228
+ [VISITOR_CLOSE_CODES.originRefused]: "this page\u2019s origin is not on the publishable key\u2019s allowed origins",
5229
+ [VISITOR_CLOSE_CODES.datasetRefused]: "`dataset` names no dataset the key reaches",
5230
+ [VISITOR_CLOSE_CODES.unavailable]: "the edge could not reach what it needed to decide just now",
5231
+ [VISITOR_CLOSE_CODES.allowanceReached]: "the project holds as many live connections as its plan allows",
5232
+ [VISITOR_CLOSE_CODES.addressLimitReached]: "this address holds as many live connections to the project as one address may",
5233
+ [VISITOR_CLOSE_CODES.ceilingReached]: "the project is at its safety ceiling of live connections"
5234
+ };
5235
+ async function* followPolls(client, request, reach, options) {
5236
+ const { signal } = options;
5237
+ for (let poll = 0; poll < LIVE_POLLS_BEFORE_SOCKET; poll += 1) {
5238
+ if (isAborted(signal)) return "aborted";
5239
+ const answer = await pollOnce2(client, request, reach.position, signal);
5240
+ if (answer === void 0) return "aborted";
5241
+ options.opened?.();
5242
+ if (answer.kind === "resync") {
5243
+ await expire(answer.reason, answer.cursor, options);
5244
+ if (isAborted(signal)) return "aborted";
5245
+ reach.position = answer.cursor;
5246
+ options.reached?.(answer.cursor);
5247
+ } else {
5248
+ const prior = reach.position;
5249
+ if (answer.changes.length > 0 && prior === void 0) {
5250
+ throw unreadable("a poll without a cursor answered changes");
5251
+ }
5252
+ for (const change of changesOf(answer.changes, answer.cursor, prior ?? answer.cursor)) {
5253
+ yield change;
5254
+ if (isAborted(signal)) return "aborted";
5255
+ }
5256
+ reach.position = answer.cursor;
5257
+ if (answer.changes.length === 0) options.reached?.(answer.cursor);
5258
+ }
5259
+ await sleep(VISITOR_POLL_INTERVAL_MS, signal);
5260
+ }
5261
+ return isAborted(signal) ? "aborted" : "socket";
5262
+ }
5263
+ chunkMWMSFIWN_cjs.__name(followPolls, "followPolls");
5264
+ async function pollOnce2(client, request, since, signal) {
5265
+ if (isAborted(signal)) return void 0;
5266
+ const url = edgeUrl(request, since, "poll", client.clientId);
5267
+ const context = { url: edgeUrl({ ...request, key: void 0 }, since, "poll", client.clientId) };
5268
+ const controller = new AbortController();
5269
+ let timedOut = false;
5270
+ const timer = setTimeout(() => {
5271
+ timedOut = true;
5272
+ controller.abort();
5273
+ }, client.timeout);
5274
+ const onAbort = /* @__PURE__ */ chunkMWMSFIWN_cjs.__name(() => controller.abort(), "onAbort");
5275
+ signal?.addEventListener("abort", onAbort, { once: true });
5276
+ try {
5277
+ let response;
5278
+ let text;
5279
+ try {
5280
+ const send = client.fetch ?? globalThis.fetch;
5281
+ response = await send(url, { method: "GET", signal: controller.signal });
5282
+ text = await response.text();
5283
+ } catch (error) {
5284
+ if (isAborted(signal)) return void 0;
5285
+ if (timedOut) {
5286
+ throw new TimeoutError(
5287
+ `The live edge did not answer a poll within ${client.timeout}ms`,
5288
+ client.timeout,
5289
+ void 0,
5290
+ void 0,
5291
+ context
5292
+ );
5293
+ }
5294
+ throw ErrorMapper.mapGenericError(
5295
+ error instanceof Error ? error : new Error(String(error)),
5296
+ context
5297
+ );
5298
+ }
5299
+ const body = parseJson(text);
5300
+ if (!response.ok) throw refusedPoll(response, body, context);
5301
+ const answer = readVisitorPollAnswer(isRecord(body) ? body["data"] : void 0);
5302
+ if (answer === null)
5303
+ throw unreadable(`a poll answer the wire does not declare: ${sample(text)}`);
5304
+ return answer;
5305
+ } finally {
5306
+ clearTimeout(timer);
5307
+ signal?.removeEventListener("abort", onAbort);
5308
+ }
5309
+ }
5310
+ chunkMWMSFIWN_cjs.__name(pollOnce2, "pollOnce");
5311
+ function refusedPoll(response, body, context) {
5312
+ const headers = {};
5313
+ response.headers.forEach((value, name) => {
5314
+ headers[name] = value;
5315
+ });
5316
+ const mapped = ErrorMapper.mapHttpError(
5317
+ { status: response.status, statusText: response.statusText, body, headers },
5318
+ { ...context, method: "GET" }
5319
+ );
5320
+ const refusal = readError(body);
5321
+ if (refusal === void 0 || !hasOwn(VISITOR_POLL_RETRY, refusal.code)) return mapped;
5322
+ const code = refusal.code;
5323
+ const retryAfterMs = parseRetryAfterMs(headerValue(headers, "Retry-After"), Date.now());
5324
+ return new LiveEdgeError(
5325
+ `The live edge refused a poll (${code}): ${refusal.messages[0] ?? mapped.message}`,
5326
+ { kind: "refused", code },
5327
+ VISITOR_POLL_RETRY[code],
5328
+ {
5329
+ statusCode: response.status,
5330
+ cause: mapped,
5331
+ ...retryAfterMs === void 0 ? {} : { retryAfterMs },
5332
+ context
5333
+ }
5334
+ );
5335
+ }
5336
+ chunkMWMSFIWN_cjs.__name(refusedPoll, "refusedPoll");
5337
+ function edgeUrl(request, since, transport, clientId) {
5338
+ const url = new URL(request.base.href);
5339
+ url.pathname = `${url.pathname.replace(/\/+$/, "")}${VISITOR_LIVE_PATH}`;
5340
+ if (transport === "socket") url.protocol = url.protocol === "http:" ? "ws:" : "wss:";
5341
+ const query = new URLSearchParams();
5342
+ if (request.key !== void 0) query.set(VISITOR_QUERY.key, request.key);
5343
+ if (request.dataset !== void 0 && request.dataset !== "") {
5344
+ query.set(VISITOR_QUERY.dataset, request.dataset);
5345
+ }
5346
+ if (since !== void 0) query.set(VISITOR_QUERY.since, since);
5347
+ if (request.types !== void 0) query.set(VISITOR_QUERY.types, request.types.join(","));
5348
+ if (request.ids !== void 0) query.set(VISITOR_QUERY.ids, request.ids.join(","));
5349
+ if (transport === "poll" && clientId !== void 0) query.set(VISITOR_QUERY.client, clientId);
5350
+ url.search = query.toString();
5351
+ return url.href;
5352
+ }
5353
+ chunkMWMSFIWN_cjs.__name(edgeUrl, "edgeUrl");
5354
+ function changesOf(changes, cursor, prior) {
5355
+ const last = changes.length - 1;
5356
+ return changes.map((change, index) => ({
5357
+ entryId: change.entryId,
5358
+ contentType: change.contentType,
5359
+ locale: change.locale,
5360
+ kind: change.kind,
5361
+ cursor: index === last ? cursor : prior,
5362
+ fresh: cursor
5363
+ }));
5364
+ }
5365
+ chunkMWMSFIWN_cjs.__name(changesOf, "changesOf");
5366
+ async function expire(reason, cursor, options) {
5367
+ const expired = ChangesExpiredError.resync(reason, cursor);
5368
+ const onExpired = options.onExpired ?? "throw";
5369
+ if (onExpired === "throw") throw expired;
5370
+ if (typeof onExpired === "function") await onExpired(expired);
5371
+ }
5372
+ chunkMWMSFIWN_cjs.__name(expire, "expire");
5373
+ function unreadable(what) {
5374
+ return new LiveEdgeError(
5375
+ `The live edge sent ${what}. This version of the SDK cannot follow it, so it stopped.`,
5376
+ { kind: "unreadable", what },
5377
+ "stop"
5378
+ );
5379
+ }
5380
+ chunkMWMSFIWN_cjs.__name(unreadable, "unreadable");
5381
+ var NEEDS_PUBLISHABLE_KEY = `Live updates at RelyIO\u2019s edge need a publishable key (\`${PUBLISHABLE_KEY_PREFIX}\u2026\`), and this client holds none. A secret or preview key is never sent to the edge; on a server, follow \`changes()\` with it instead. Nothing was sent.`;
5382
+ function sample(value) {
5383
+ const text = typeof value === "string" ? value : `(${typeof value})`;
5384
+ return text.length > 200 ? `${text.slice(0, 200)}\u2026` : text;
5385
+ }
5386
+ chunkMWMSFIWN_cjs.__name(sample, "sample");
5387
+ function parseJson(text) {
5388
+ try {
5389
+ return JSON.parse(text);
5390
+ } catch {
5391
+ return void 0;
5392
+ }
5393
+ }
5394
+ chunkMWMSFIWN_cjs.__name(parseJson, "parseJson");
5395
+ function isRecord(value) {
5396
+ return typeof value === "object" && value !== null && !Array.isArray(value);
5397
+ }
5398
+ chunkMWMSFIWN_cjs.__name(isRecord, "isRecord");
5399
+ function hasOwn(object, key) {
5400
+ return Object.prototype.hasOwnProperty.call(object, key);
5401
+ }
5402
+ chunkMWMSFIWN_cjs.__name(hasOwn, "hasOwn");
5403
+
4370
5404
  // src/config/dev-detection.ts
4371
5405
  function sandboxStateOf(resolution) {
4372
5406
  return resolution.state === "active" ? resolution.sandbox : resolution;
@@ -4477,7 +5511,7 @@ var DEFAULT_CONFIG = {
4477
5511
  environment: "production",
4478
5512
  headers: {},
4479
5513
  browser: {
4480
- includeCredentials: true,
5514
+ includeCredentials: false,
4481
5515
  useRelativeUrls: false
4482
5516
  },
4483
5517
  cache: {
@@ -4509,45 +5543,48 @@ function validateConfig(config) {
4509
5543
  if (config.baseUrl != null && typeof config.baseUrl !== "string") {
4510
5544
  throw new Error("Base URL must be a string");
4511
5545
  }
4512
- if (config.timeout != null && (typeof config.timeout !== "number" || config.timeout < 0)) {
4513
- throw new Error("Timeout must be a positive number");
5546
+ if (config.liveUrl != null && typeof config.liveUrl !== "string") {
5547
+ throw new Error("liveUrl must be a string");
5548
+ }
5549
+ if (config.webSocket != null && typeof config.webSocket !== "function") {
5550
+ throw new Error("webSocket must be a WebSocket class");
4514
5551
  }
4515
- if (config.retryAttempts != null && (typeof config.retryAttempts !== "number" || config.retryAttempts < 0)) {
4516
- throw new Error("Retry attempts must be a positive number");
5552
+ if (config.timeout != null) assertRequestTimeout(config.timeout);
5553
+ if (config.retryAttempts != null && !(Number.isInteger(config.retryAttempts) && config.retryAttempts >= 1)) {
5554
+ throw new Error(
5555
+ `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)}`
5556
+ );
4517
5557
  }
4518
5558
  }
4519
5559
  chunkMWMSFIWN_cjs.__name(validateConfig, "validateConfig");
4520
5560
  var devOverrideNotified = false;
4521
5561
  function resolveConfig(userConfig) {
4522
5562
  validateConfig(userConfig);
4523
- const merged = {
4524
- ...DEFAULT_CONFIG,
4525
- ...userConfig,
4526
- headers: {
4527
- ...DEFAULT_CONFIG.headers,
4528
- ...userConfig.headers
4529
- },
4530
- browser: {
4531
- ...DEFAULT_CONFIG.browser,
4532
- ...userConfig.browser
4533
- },
4534
- cache: {
4535
- ...DEFAULT_CONFIG.cache,
4536
- ...userConfig.cache
4537
- },
4538
- realtime: {
4539
- ...DEFAULT_CONFIG.realtime,
4540
- ...userConfig.realtime
4541
- },
4542
- debug: {
4543
- ...DEFAULT_CONFIG.debug,
4544
- ...userConfig.debug
4545
- }
4546
- };
4547
- const dev = resolveDevOverride(userConfig.dev);
5563
+ return withHandshake(withDefaults(DEFAULT_CONFIG, userConfig, DEFAULT_CONFIG));
5564
+ }
5565
+ chunkMWMSFIWN_cjs.__name(resolveConfig, "resolveConfig");
5566
+ function resolveConfigUpdate(configured, update) {
5567
+ validateConfig(update);
5568
+ const base = update.apiKey === void 0 ? configured : withoutKeyHeader(configured);
5569
+ return withHandshake(withDefaults(base, update, DEFAULT_CONFIG));
5570
+ }
5571
+ chunkMWMSFIWN_cjs.__name(resolveConfigUpdate, "resolveConfigUpdate");
5572
+ function withConfiguredKey(configured, apiKey) {
5573
+ return { ...withoutKeyHeader(configured), apiKey };
5574
+ }
5575
+ chunkMWMSFIWN_cjs.__name(withConfiguredKey, "withConfiguredKey");
5576
+ function withoutKeyHeader(configured) {
5577
+ return { ...configured, headers: layerHeaders(configured.headers, { [API_KEY_HEADER]: null }) };
5578
+ }
5579
+ chunkMWMSFIWN_cjs.__name(withoutKeyHeader, "withoutKeyHeader");
5580
+ function withHandshake(configured) {
5581
+ const dev = resolveDevOverride(configured.dev);
5582
+ const merged = withDefaults(
5583
+ configured,
5584
+ dev.state === "active" ? { apiKey: dev.apiKey, dataset: dev.sandbox.datasetId } : {},
5585
+ DEFAULT_CONFIG
5586
+ );
4548
5587
  if (dev.state === "active") {
4549
- merged.apiKey = dev.apiKey;
4550
- merged.dataset = dev.sandbox.datasetId;
4551
5588
  if (!devOverrideNotified) {
4552
5589
  devOverrideNotified = true;
4553
5590
  console.info(
@@ -4565,30 +5602,69 @@ function resolveConfig(userConfig) {
4565
5602
  `[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.`
4566
5603
  );
4567
5604
  }
4568
- return { config: merged, sandbox: sandboxStateOf(dev) };
5605
+ return { config: merged, sandbox: sandboxStateOf(dev), configured };
4569
5606
  }
4570
- chunkMWMSFIWN_cjs.__name(resolveConfig, "resolveConfig");
5607
+ chunkMWMSFIWN_cjs.__name(withHandshake, "withHandshake");
5608
+ function withDefaults(base, given, defaults) {
5609
+ const merged = new Map(Object.entries(base));
5610
+ const givenValues = new Map(Object.entries(given ?? {}));
5611
+ const defaultValues = new Map(Object.entries(defaults));
5612
+ for (const key of /* @__PURE__ */ new Set([...merged.keys(), ...givenValues.keys()])) {
5613
+ const fallback = merged.get(key);
5614
+ const value = givenValues.get(key);
5615
+ const givenBag = isOptionBag(value) ? value : void 0;
5616
+ if (isOptionBag(fallback) && key === "headers") {
5617
+ merged.set(key, layerHeaders(fallback, givenBag));
5618
+ } else if (isOptionBag(fallback)) {
5619
+ const defaultBag = defaultValues.get(key);
5620
+ merged.set(key, withDefaults(fallback, givenBag, isOptionBag(defaultBag) ? defaultBag : {}));
5621
+ } else if (value === null) {
5622
+ if (defaultValues.has(key)) merged.set(key, defaultValues.get(key));
5623
+ else merged.delete(key);
5624
+ } else if (value !== void 0) {
5625
+ merged.set(key, value);
5626
+ }
5627
+ }
5628
+ return Object.fromEntries(merged);
5629
+ }
5630
+ chunkMWMSFIWN_cjs.__name(withDefaults, "withDefaults");
5631
+ function isOptionBag(value) {
5632
+ return typeof value === "object" && value !== null && !Array.isArray(value);
5633
+ }
5634
+ chunkMWMSFIWN_cjs.__name(isOptionBag, "isOptionBag");
4571
5635
 
4572
5636
  // src/client/create-client.ts
4573
5637
  var API_VERSION_SEGMENT = "v1";
4574
5638
  var _TypedRelyIOClient = class _TypedRelyIOClient {
4575
5639
  constructor(config) {
5640
+ /**
5641
+ * The one HTTP client every sub-client sends through. `updateConfig()` and `setApiKey()` change it
5642
+ * in place rather than replacing it, so a sub-client taken before either sends through the change.
5643
+ */
4576
5644
  chunkMWMSFIWN_cjs.__publicField(this, "httpClient");
4577
5645
  chunkMWMSFIWN_cjs.__publicField(this, "config");
5646
+ /**
5647
+ * The configuration as `createClient()`, `setApiKey()` and `updateConfig()` gave it, before the
5648
+ * `relyio dev` handshake: what the next update is laid over, and what `withLocale()` starts from.
5649
+ */
5650
+ chunkMWMSFIWN_cjs.__publicField(this, "configured");
4578
5651
  chunkMWMSFIWN_cjs.__publicField(this, "_sandbox");
4579
- chunkMWMSFIWN_cjs.__publicField(this, "_search");
4580
- chunkMWMSFIWN_cjs.__publicField(this, "_batch");
4581
- chunkMWMSFIWN_cjs.__publicField(this, "_adhocQuery");
4582
5652
  chunkMWMSFIWN_cjs.__publicField(this, "_schema");
5653
+ /**
5654
+ * The client's locale as it is when a call is made. Every sub-client and builder is handed this
5655
+ * rather than a copy of the locale, so one taken before `updateConfig({ locale })` sends the new
5656
+ * locale, or none after `locale: null` (REL-1453).
5657
+ */
5658
+ chunkMWMSFIWN_cjs.__publicField(this, "currentLocale", /* @__PURE__ */ chunkMWMSFIWN_cjs.__name(() => this.config.locale, "currentLocale"));
5659
+ /** The live edge's poll client id: chosen once, when first needed, and kept for this client's life. */
5660
+ chunkMWMSFIWN_cjs.__publicField(this, "_liveClientId");
4583
5661
  const { schema, ...clientConfig } = config;
4584
5662
  this._schema = schema;
4585
5663
  const resolved = resolveConfig(clientConfig);
4586
5664
  this.config = resolved.config;
5665
+ this.configured = resolved.configured;
4587
5666
  this._sandbox = resolved.sandbox;
4588
5667
  this.httpClient = new HttpClient(this.config);
4589
- this._search = new SearchClient(this.httpClient, this.config.locale);
4590
- this._batch = new BatchClient(this.httpClient);
4591
- this._adhocQuery = new AdhocQueryClient(this.httpClient, this.config.locale);
4592
5668
  }
4593
5669
  // ============================================================================
4594
5670
  // CORE CLIENTS
@@ -4617,10 +5693,10 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
4617
5693
  return this._sandbox;
4618
5694
  }
4619
5695
  /**
4620
- * Get the ad-hoc query client
5696
+ * Get the ad-hoc query client. Every query it sends is in the client's locale as it is then.
4621
5697
  */
4622
5698
  get query() {
4623
- return this._adhocQuery;
5699
+ return new AdhocQueryClient(this.httpClient, this.currentLocale);
4624
5700
  }
4625
5701
  // ============================================================================
4626
5702
  // CONTENT OPERATIONS
@@ -4640,7 +5716,7 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
4640
5716
  * ```
4641
5717
  */
4642
5718
  content(contentType) {
4643
- return new ContentClient(this.httpClient, contentType, this.config.locale);
5719
+ return new ContentClient(this.httpClient, contentType, this.currentLocale);
4644
5720
  }
4645
5721
  /**
4646
5722
  * Get an asset client for a specific asset type.
@@ -4661,7 +5737,7 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
4661
5737
  * ```
4662
5738
  */
4663
5739
  asset(assetType) {
4664
- return new AssetClient(this.httpClient, assetType, this.config.locale);
5740
+ return new AssetClient(this.httpClient, assetType, this.currentLocale);
4665
5741
  }
4666
5742
  /**
4667
5743
  * Get a singleton client for a specific singleton key
@@ -4676,7 +5752,7 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
4676
5752
  * ```
4677
5753
  */
4678
5754
  singleton(key) {
4679
- return new SingletonClient(this.httpClient, key, this.config.locale);
5755
+ return new SingletonClient(this.httpClient, key, this.currentLocale);
4680
5756
  }
4681
5757
  /**
4682
5758
  * Get a settings client for a specific settings group
@@ -4690,7 +5766,7 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
4690
5766
  * ```
4691
5767
  */
4692
5768
  settings(groupId) {
4693
- return new SettingsClient(this.httpClient, groupId, this.config.locale);
5769
+ return new SettingsClient(this.httpClient, groupId, this.currentLocale);
4694
5770
  }
4695
5771
  // ============================================================================
4696
5772
  // SEARCH OPERATIONS
@@ -4712,7 +5788,7 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
4712
5788
  * ```
4713
5789
  */
4714
5790
  search(query) {
4715
- return this._search.search(query);
5791
+ return this.searchClient().search(query);
4716
5792
  }
4717
5793
  /**
4718
5794
  * Start an autocomplete query for search suggestions
@@ -4728,7 +5804,7 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
4728
5804
  * ```
4729
5805
  */
4730
5806
  autocomplete(query) {
4731
- return this._search.autocomplete(query);
5807
+ return this.searchClient().autocomplete(query);
4732
5808
  }
4733
5809
  /**
4734
5810
  * Find content similar to a given entry using vector search
@@ -4743,13 +5819,17 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
4743
5819
  * ```
4744
5820
  */
4745
5821
  similar(sourceId) {
4746
- return this._search.similar(sourceId);
5822
+ return this.searchClient().similar(sourceId);
4747
5823
  }
4748
5824
  // ============================================================================
4749
5825
  // BATCH OPERATIONS
4750
5826
  // ============================================================================
4751
5827
  /**
4752
- * Start a batch operation for bulk create/update/delete
5828
+ * Start a batch operation for bulk create/update/delete.
5829
+ *
5830
+ * By default each operation lands or fails on its own. `atomic: true` makes the batch one
5831
+ * transaction, which the API accepts only when every operation is the same kind of write and none
5832
+ * writes a singleton that does not exist yet.
4753
5833
  *
4754
5834
  * @example
4755
5835
  * ```typescript
@@ -4758,13 +5838,23 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
4758
5838
  * .update('product', 'entry_abc', { content: { price: 899 } })
4759
5839
  * .delete('product', 'entry_xyz')
4760
5840
  * .upsertSingleton('homepage', { content: { hero: 'Welcome!' } })
4761
- * .execute({ atomic: true })
5841
+ * .execute()
4762
5842
  *
4763
5843
  * console.log(result.summary.succeeded)
5844
+ *
5845
+ * // All or nothing: one kind of write
5846
+ * await relyio.batch()
5847
+ * .update('product', 'entry_abc', { content: { price: 899 } })
5848
+ * .update('product', 'entry_def', { content: { price: 799 } })
5849
+ * .execute({ atomic: true })
4764
5850
  * ```
4765
5851
  */
4766
5852
  batch() {
4767
- return this._batch.create();
5853
+ return new BatchClient(this.httpClient, this.currentLocale).create();
5854
+ }
5855
+ /** The search client, whose builders send the client's locale as it is when each one is sent. */
5856
+ searchClient() {
5857
+ return new SearchClient(this.httpClient, this.currentLocale);
4768
5858
  }
4769
5859
  // ============================================================================
4770
5860
  // CHANGES FEED
@@ -4776,13 +5866,15 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
4776
5866
  * Lazy — nothing is requested until the first `next()`. It runs until
4777
5867
  * `signal` aborts, which ends the `for await` cleanly rather than throwing.
4778
5868
  * A change names what changed; refetch it through the ordinary read path
4779
- * (`INV-SS-CHANGE-IS-IDS`).
5869
+ * (`INV-SS-CHANGE-IS-IDS`), naming its `fresh` so no cache answers with the
5870
+ * body from before it (`FeedChange`).
4780
5871
  *
4781
5872
  * @example
4782
5873
  * ```typescript
4783
5874
  * const controller = new AbortController()
4784
5875
  * for await (const change of relyio.changes({ since: cursor, types: ['post'], signal: controller.signal })) {
4785
- * // { entryId, contentType, locale, kind, cursor }
5876
+ * // { entryId, contentType, locale, kind, cursor, fresh }
5877
+ * await relyio.content(change.contentType).findById(change.entryId).fresh(change.fresh).execute()
4786
5878
  * }
4787
5879
  * ```
4788
5880
  *
@@ -4790,13 +5882,60 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
4790
5882
  * ```typescript
4791
5883
  * for await (const change of relyio.changes({
4792
5884
  * since: cursor,
4793
- * onExpired: async () => invalidateEverything(),
5885
+ * onExpired: async expired => refetchEverything(expired.fresh),
4794
5886
  * })) { … }
4795
5887
  * ```
4796
5888
  */
4797
5889
  changes(options = {}) {
4798
5890
  return pollChanges(this.httpClient, options);
4799
5891
  }
5892
+ /**
5893
+ * Follow the dataset's **published** changes at RelyIO's live edge, with a publishable key
5894
+ * (`rly_pub_…`): each change as ids, in order — the transport a browser follows changes with.
5895
+ *
5896
+ * A WebSocket to the edge, from `since` or the dataset's current position; where a socket cannot
5897
+ * open — the runtime has none, or the network refuses it — it polls the edge instead, about every
5898
+ * fifteen seconds, from the same cursor. The edge is `liveUrl`, or derived from the default
5899
+ * `baseUrl` (see `RelyIOConfig.liveUrl`).
5900
+ *
5901
+ * One subscription ends only when `signal` aborts, which ends the `for await` cleanly; every other
5902
+ * end — a socket a deploy dropped, a refusal, a refused poll — is thrown as a `LiveEdgeError`
5903
+ * that says whether to stop, retry or back off. Hand it to `followChanges()`, the one loop that
5904
+ * reconnects from the last cursor with jittered back-off and honours those answers:
5905
+ *
5906
+ * @example
5907
+ * ```typescript
5908
+ * const relyio = createClient({ apiKey: 'rly_pub_…' })
5909
+ * const controller = new AbortController()
5910
+ * await followChanges(from => relyio.liveChanges({ ...from, types: ['post'] }), {
5911
+ * signal: controller.signal,
5912
+ * // `fresh` names a position no cache holds a body for from before the change.
5913
+ * onChange: change =>
5914
+ * relyio.content(change.contentType).findById(change.entryId).fresh(change.fresh).execute(),
5915
+ * onResync: resync => refetchEverything(resync.cause === 'expired' ? resync.expired.fresh : undefined),
5916
+ * })
5917
+ * ```
5918
+ *
5919
+ * A client without a publishable key sends nothing: a secret or preview key is never put in the
5920
+ * edge's URL. On a server, follow `changes()` with it instead.
5921
+ */
5922
+ liveChanges(options = {}) {
5923
+ const { apiKey, dataset, baseUrl, liveUrl, fetch: fetch2, webSocket, timeout } = this.config;
5924
+ this._liveClientId ?? (this._liveClientId = randomClientId());
5925
+ return subscribeLiveEdge(
5926
+ {
5927
+ apiKey,
5928
+ dataset,
5929
+ baseUrl,
5930
+ liveUrl,
5931
+ fetch: fetch2,
5932
+ webSocket,
5933
+ timeout,
5934
+ clientId: this._liveClientId
5935
+ },
5936
+ options
5937
+ );
5938
+ }
4800
5939
  /**
4801
5940
  * The dataset's current position in the changes feed, as a cursor to start
4802
5941
  * `changes({ since })` or `followChanges(…, { since })` from. One
@@ -4838,7 +5977,7 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
4838
5977
  * ```
4839
5978
  */
4840
5979
  adhoc() {
4841
- return this._adhocQuery.build();
5980
+ return this.query.build();
4842
5981
  }
4843
5982
  /**
4844
5983
  * Execute a predefined query by name (from schema)
@@ -4923,16 +6062,53 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
4923
6062
  }
4924
6063
  }
4925
6064
  /**
4926
- * Update the configuration and reinitialize all clients
6065
+ * Change some of the client's options and keep the rest.
6066
+ *
6067
+ * What you pass is laid over the configuration the client holds, field by field:
6068
+ *
6069
+ * - an option you name replaces the value the client holds;
6070
+ * - an option left out, or given as `undefined`, keeps the value the client holds;
6071
+ * - an option with no default given as `null` is cleared — `locale`, `dataset`, `apiKey`, `dev`,
6072
+ * `fetch`, `browser.onCorsError`, `realtime.wsUrl`, `debug.logger` — so the client runs as though
6073
+ * it had never been given one. An option with a default takes no `null`;
6074
+ * - a bag of options you name — `browser`, `cache`, `realtime`, `debug` — keeps every field of it
6075
+ * you do not name, so `updateConfig({ browser: { includeCredentials: true } })` keeps a
6076
+ * configured `onCorsError`;
6077
+ * - `headers` is laid over the headers the client sends, a name matched without regard to case: a
6078
+ * header you name is added or replaces every spelling of it, every other one is still sent, and a
6079
+ * header given as `null` is no longer sent;
6080
+ * - naming `apiKey` — a key, or `null` — replaces every key the client holds, one given in
6081
+ * `headers` included.
6082
+ *
6083
+ * The configuration it is laid over is what `createClient()`, `setApiKey()` and earlier updates
6084
+ * gave the client. The `relyio dev` handshake is read again, as `createClient()` reads it: while a
6085
+ * sandbox is active its key and dataset are sent over the configured ones, and once it has gone
6086
+ * the configured ones are.
6087
+ *
6088
+ * The client's one HTTP client is changed in place, so every request from now on is sent as the
6089
+ * update says — including one from a `content()`, `asset()`, `singleton()` or `settings()`
6090
+ * sub-client, a `query` client, or a builder such as `search()` or `batch()`, taken before the
6091
+ * call. The locale reaches them too: each reads the client's locale when it sends, so a new
6092
+ * `locale` is the default of its next request and `locale: null` leaves that request with none. A
6093
+ * locale the call names, and a sub-client's own `withLocale()`, still win over it. A client made
6094
+ * by `withLocale()` is a client of its own: an update of this one does not reach it.
6095
+ *
6096
+ * @example
6097
+ * ```typescript
6098
+ * const relyio = createClient({ apiKey, headers: { 'X-Tenant': 'acme' }, browser: { onCorsError } })
6099
+ *
6100
+ * relyio.updateConfig({ headers: { 'X-Trace': traceId } }) // X-Tenant is still sent
6101
+ * relyio.updateConfig({ browser: { includeCredentials: true } }) // onCorsError is kept
6102
+ * relyio.updateConfig({ headers: { 'X-Tenant': null } }) // X-Tenant is no longer sent
6103
+ * relyio.updateConfig({ locale: null }) // reads name no locale of the client's
6104
+ * ```
4927
6105
  */
4928
- updateConfig(newConfig) {
4929
- const resolved = resolveConfig({ ...this.config, ...newConfig });
6106
+ updateConfig(update) {
6107
+ const resolved = resolveConfigUpdate(this.configured, update);
4930
6108
  this.config = resolved.config;
6109
+ this.configured = resolved.configured;
4931
6110
  this._sandbox = resolved.sandbox;
4932
- this.httpClient = new HttpClient(this.config);
4933
- this._search = new SearchClient(this.httpClient, this.config.locale);
4934
- this._batch = new BatchClient(this.httpClient);
4935
- this._adhocQuery = new AdhocQueryClient(this.httpClient, this.config.locale);
6111
+ this.httpClient.configure(this.config);
4936
6112
  }
4937
6113
  /**
4938
6114
  * Get the current configuration
@@ -4941,11 +6117,31 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
4941
6117
  return { ...this.config };
4942
6118
  }
4943
6119
  /**
4944
- * Set the API key
6120
+ * Replace the API key every later request sends as `X-API-Key`.
6121
+ *
6122
+ * It reaches every request this client makes from now on, including those of a `content()`,
6123
+ * `asset()` or other sub-client taken before the call, and `getConfig().apiKey` reports it. A key
6124
+ * given in `headers`, under any spelling, is replaced with it and does not come back on a later
6125
+ * `updateConfig()`. A client already derived with `withLocale()` is a client of its own and keeps
6126
+ * its key.
6127
+ *
6128
+ * An empty or blank key is no key, as it is when passed to `createClient`: the next request
6129
+ * rejects with `MissingApiKeyError` and nothing is sent.
6130
+ *
6131
+ * Under an active `relyio dev` sandbox the sandbox's scoped key stands, as it does over the key
6132
+ * passed to `createClient` — replacing the configured key does not retarget the client at the
6133
+ * sandbox's dataset with a key the sandbox did not issue.
6134
+ *
6135
+ * @example
6136
+ * ```typescript
6137
+ * const relyio = createClient({ apiKey: process.env.RELYIO_API_KEY! })
6138
+ * relyio.setApiKey(rotatedKey) // the next read sends `X-API-Key: <rotatedKey>`
6139
+ * ```
4945
6140
  */
4946
6141
  setApiKey(apiKey) {
4947
- this.config.apiKey = apiKey;
4948
- this.httpClient.setAuthToken(apiKey);
6142
+ this.configured = withConfiguredKey(this.configured, apiKey);
6143
+ if (this._sandbox.state === "active") return;
6144
+ this.httpClient.setApiKey(apiKey);
4949
6145
  }
4950
6146
  /**
4951
6147
  * Get the HTTP client instance (for advanced usage)
@@ -4959,9 +6155,12 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
4959
6155
  /**
4960
6156
  * Create a locale-scoped client
4961
6157
  *
4962
- * Every read the returned client builds — `content()`, `asset()`, `singleton()`, `settings()`,
4963
- * `search()`, `similar()`, `autocomplete()`, `executeQuery()` and the ad-hoc queries — is in this
4964
- * locale unless the call names one of its own, which wins.
6158
+ * Every call the returned client builds is in this locale unless the call names one of its own,
6159
+ * which wins: every read — `content()`, `asset()`, `singleton()`, `settings()`, `search()`,
6160
+ * `similar()`, `autocomplete()`, `executeQuery()` and the ad-hoc queries — and every write and
6161
+ * version-history read — `create()` and `update()`, the singleton and settings writes, the asset
6162
+ * file writes, `versions()` and `version()`, and each operation `batch()` builds. `restore()`
6163
+ * always names its locale; a delete, and a `batch().raw()` operation, carry none of the client's.
4965
6164
  *
4966
6165
  * Type-safe when schema is provided: autocompletes valid locale codes!
4967
6166
  *
@@ -4969,10 +6168,11 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
4969
6168
  * ```typescript
4970
6169
  * const spanish = relyio.withLocale('es-ES') // ← Autocompletes with schema!
4971
6170
  *
4972
- * // Reads use Spanish
6171
+ * // Reads and writes use Spanish
4973
6172
  * const products = await spanish.content('product').query().execute()
4974
6173
  * const homepage = await spanish.singleton('homepage').fetch()
4975
6174
  * const results = await spanish.search('zapatos').execute()
6175
+ * await spanish.content('product').update(id, { title: 'Zapatos' })
4976
6176
  *
4977
6177
  * // Original client unchanged
4978
6178
  * const englishProducts = await relyio.content('product').query().execute()
@@ -4980,7 +6180,7 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
4980
6180
  */
4981
6181
  withLocale(locale) {
4982
6182
  const newConfig = {
4983
- ...this.getConfig(),
6183
+ ...this.configured,
4984
6184
  locale,
4985
6185
  schema: this._schema
4986
6186
  };
@@ -5028,13 +6228,25 @@ function createClient(config) {
5028
6228
  return new TypedRelyIOClient(config);
5029
6229
  }
5030
6230
  chunkMWMSFIWN_cjs.__name(createClient, "createClient");
6231
+ function randomClientId() {
6232
+ const bytes = new Uint8Array(16);
6233
+ const random = globalThis.crypto;
6234
+ if (typeof random?.getRandomValues === "function") {
6235
+ random.getRandomValues.call(random, bytes);
6236
+ } else {
6237
+ for (let index = 0; index < bytes.length; index += 1) {
6238
+ bytes[index] = Math.floor(Math.random() * 256);
6239
+ }
6240
+ }
6241
+ return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
6242
+ }
6243
+ chunkMWMSFIWN_cjs.__name(randomClientId, "randomClientId");
5031
6244
 
5032
6245
  exports.AdhocQueryBuilder = AdhocQueryBuilder;
5033
6246
  exports.AdhocQueryClient = AdhocQueryClient;
5034
6247
  exports.AssetClient = AssetClient;
5035
6248
  exports.AssetFileBuilder = AssetFileBuilder;
5036
6249
  exports.AssetUploadBuilder = AssetUploadBuilder;
5037
- exports.AuthenticationError = AuthenticationError;
5038
6250
  exports.AuthorizationError = AuthorizationError;
5039
6251
  exports.AutocompleteBuilder = AutocompleteBuilder;
5040
6252
  exports.BaseValidationError = BaseValidationError;
@@ -5050,21 +6262,21 @@ exports.FileUploadError = FileUploadError;
5050
6262
  exports.FileValidationError = FileValidationError;
5051
6263
  exports.HttpClient = HttpClient;
5052
6264
  exports.InvalidApiKeyError = InvalidApiKeyError;
5053
- exports.InvalidFieldTypeError = InvalidFieldTypeError;
5054
6265
  exports.InvalidPayloadError = InvalidPayloadError;
6266
+ exports.LIVE_KEEPALIVE_INTERVAL_MS = LIVE_KEEPALIVE_INTERVAL_MS;
6267
+ exports.LiveEdgeError = LiveEdgeError;
5055
6268
  exports.MethodNotAllowedError = MethodNotAllowedError;
5056
6269
  exports.MissingApiKeyError = MissingApiKeyError;
5057
6270
  exports.NetworkError = NetworkError;
6271
+ exports.PUBLISHABLE_KEY_PREFIX = PUBLISHABLE_KEY_PREFIX;
5058
6272
  exports.PayloadTooLargeError = PayloadTooLargeError;
5059
6273
  exports.PlanFeatureUnavailableError = PlanFeatureUnavailableError;
5060
6274
  exports.RateLimitError = RateLimitError;
5061
6275
  exports.ReadOnlyKeyError = ReadOnlyKeyError;
5062
- exports.RequiredFieldError = RequiredFieldError;
5063
6276
  exports.ResourceConflictError = ResourceConflictError;
5064
6277
  exports.ResourceExpiredError = ResourceExpiredError;
5065
6278
  exports.ResourceNotFoundError = ResourceNotFoundError;
5066
6279
  exports.SDKError = SDKError;
5067
- exports.SchemaValidationError = SchemaValidationError;
5068
6280
  exports.SearchBuilder = SearchBuilder;
5069
6281
  exports.SearchClient = SearchClient;
5070
6282
  exports.ServerError = ServerError;
@@ -5077,15 +6289,17 @@ exports.TransactionRolledBackError = TransactionRolledBackError;
5077
6289
  exports.TypedRelyIOClient = TypedRelyIOClient;
5078
6290
  exports.UnknownContractError = UnknownContractError;
5079
6291
  exports.UsageQuotaExceededError = UsageQuotaExceededError;
6292
+ exports.VISITOR_ADMISSION_RETRY_AFTER_SECONDS = VISITOR_ADMISSION_RETRY_AFTER_SECONDS;
5080
6293
  exports.ValidationError = ValidationError;
5081
6294
  exports.createClient = createClient;
5082
6295
  exports.entityTagOf = entityTagOf;
5083
6296
  exports.isAborted = isAborted;
5084
6297
  exports.isAssetEntry = isAssetEntry;
6298
+ exports.isPublishableKey = isPublishableKey;
5085
6299
  exports.isResourceNotFound = isResourceNotFound;
5086
6300
  exports.matchRoute = matchRoute;
5087
6301
  exports.serviceInstructedDelayMs = serviceInstructedDelayMs;
5088
6302
  exports.sleep = sleep;
5089
6303
  exports.validateFile = validateFile;
5090
- //# sourceMappingURL=chunk-GCMQUM6N.cjs.map
5091
- //# sourceMappingURL=chunk-GCMQUM6N.cjs.map
6304
+ //# sourceMappingURL=chunk-XIE7WJV3.cjs.map
6305
+ //# sourceMappingURL=chunk-XIE7WJV3.cjs.map