@relyio/sdk 0.4.0 → 0.6.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 (31) hide show
  1. package/CHANGELOG.md +193 -0
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/{chunk-43VPZT2G.js → chunk-7ABNXVWL.js} +464 -200
  4. package/dist/{chunk-HAQELDZN.cjs → chunk-DY2XT7OF.cjs} +2 -2
  5. package/dist/{chunk-XS32U2UP.cjs → chunk-GCMQUM6N.cjs} +466 -199
  6. package/dist/{chunk-XY5KFEFY.js → chunk-XJVY2CLY.js} +2 -2
  7. package/dist/client/index.cjs +3 -3
  8. package/dist/client/index.d.cts +5 -4
  9. package/dist/client/index.d.ts +5 -4
  10. package/dist/client/index.js +1 -1
  11. package/dist/define-content-type-CDB3kyeA.d.cts +1173 -0
  12. package/dist/define-content-type-CDB3kyeA.d.ts +1173 -0
  13. package/dist/{define-schema-DzYXk4HL.d.ts → define-schema-1kr2HIzN.d.cts} +25 -347
  14. package/dist/{define-schema-2xbHgtK-.d.cts → define-schema-ChvAzY8H.d.ts} +25 -347
  15. package/dist/{index-BzOFJJKS.d.ts → index-Df1VsfJR.d.cts} +334 -72
  16. package/dist/{index-BvO6HDtY.d.cts → index-XqoGUaAY.d.ts} +334 -72
  17. package/dist/{index-DFehx5sI.d.cts → index-d1Kz1e19.d.cts} +191 -29
  18. package/dist/{index-FDak5ofk.d.ts → index-yPbhZc6q.d.ts} +191 -29
  19. package/dist/index.cjs +161 -68
  20. package/dist/index.d.cts +302 -8
  21. package/dist/index.d.ts +302 -8
  22. package/dist/index.js +90 -3
  23. package/dist/schema/index.cjs +35 -35
  24. package/dist/schema/index.d.cts +11 -7
  25. package/dist/schema/index.d.ts +11 -7
  26. package/dist/schema/index.js +1 -1
  27. package/dist/types/index.d.cts +2 -2
  28. package/dist/types/index.d.ts +2 -2
  29. package/package.json +3 -3
  30. package/dist/define-content-type-CBfnTWSS.d.cts +0 -716
  31. package/dist/define-content-type-CBfnTWSS.d.ts +0 -716
@@ -69,44 +69,6 @@ var _SDKError = class _SDKError extends Error {
69
69
  __name(_SDKError, "SDKError");
70
70
  var SDKError = _SDKError;
71
71
 
72
- // src/errors/auth-errors.ts
73
- var _AuthenticationError = class _AuthenticationError extends SDKError {
74
- constructor(message = "Authentication failed", statusCode = 401, cause, context) {
75
- super(message, statusCode, cause, context);
76
- __publicField(this, "code", "AUTHENTICATION_ERROR");
77
- __publicField(this, "retryable", false);
78
- }
79
- getUserMessage() {
80
- return "Authentication failed. Please check your API key and try again.";
81
- }
82
- };
83
- __name(_AuthenticationError, "AuthenticationError");
84
- var AuthenticationError = _AuthenticationError;
85
- var _AuthorizationError = class _AuthorizationError extends SDKError {
86
- constructor(message = "Authorization failed", statusCode = 403, cause, context) {
87
- super(message, statusCode, cause, context);
88
- __publicField(this, "code", "AUTHORIZATION_ERROR");
89
- __publicField(this, "retryable", false);
90
- }
91
- getUserMessage() {
92
- return "You do not have permission to access this resource.";
93
- }
94
- };
95
- __name(_AuthorizationError, "AuthorizationError");
96
- var AuthorizationError = _AuthorizationError;
97
- var _InvalidApiKeyError = class _InvalidApiKeyError extends SDKError {
98
- constructor(message = "Invalid API key", cause, context) {
99
- super(message, 401, cause, context);
100
- __publicField(this, "code", "INVALID_API_KEY");
101
- __publicField(this, "retryable", false);
102
- }
103
- getUserMessage() {
104
- return "Invalid API key. Please check your API key and try again.";
105
- }
106
- };
107
- __name(_InvalidApiKeyError, "InvalidApiKeyError");
108
- var InvalidApiKeyError = _InvalidApiKeyError;
109
-
110
72
  // src/errors/contract-errors.ts
111
73
  var _MissingApiKeyError = class _MissingApiKeyError extends SDKError {
112
74
  constructor(message = "No API key was sent", context) {
@@ -267,6 +229,44 @@ var _UnknownContractError = class _UnknownContractError extends SDKError {
267
229
  __name(_UnknownContractError, "UnknownContractError");
268
230
  var UnknownContractError = _UnknownContractError;
269
231
 
232
+ // src/errors/auth-errors.ts
233
+ var _AuthenticationError = class _AuthenticationError extends SDKError {
234
+ constructor(message = "Authentication failed", statusCode = 401, cause, context) {
235
+ super(message, statusCode, cause, context);
236
+ __publicField(this, "code", "AUTHENTICATION_ERROR");
237
+ __publicField(this, "retryable", false);
238
+ }
239
+ getUserMessage() {
240
+ return "Authentication failed. Please check your API key and try again.";
241
+ }
242
+ };
243
+ __name(_AuthenticationError, "AuthenticationError");
244
+ var AuthenticationError = _AuthenticationError;
245
+ var _AuthorizationError = class _AuthorizationError extends SDKError {
246
+ constructor(message = "Authorization failed", statusCode = 403, cause, context) {
247
+ super(message, statusCode, cause, context);
248
+ __publicField(this, "code", "AUTHORIZATION_ERROR");
249
+ __publicField(this, "retryable", false);
250
+ }
251
+ getUserMessage() {
252
+ return "You do not have permission to access this resource.";
253
+ }
254
+ };
255
+ __name(_AuthorizationError, "AuthorizationError");
256
+ var AuthorizationError = _AuthorizationError;
257
+ var _InvalidApiKeyError = class _InvalidApiKeyError extends SDKError {
258
+ constructor(message = "Invalid API key", cause, context) {
259
+ super(message, 401, cause, context);
260
+ __publicField(this, "code", "INVALID_API_KEY");
261
+ __publicField(this, "retryable", false);
262
+ }
263
+ getUserMessage() {
264
+ return "Invalid API key. Please check your API key and try again.";
265
+ }
266
+ };
267
+ __name(_InvalidApiKeyError, "InvalidApiKeyError");
268
+ var InvalidApiKeyError = _InvalidApiKeyError;
269
+
270
270
  // src/errors/content-errors.ts
271
271
  var _ResourceNotFoundError = class _ResourceNotFoundError extends SDKError {
272
272
  constructor(message = "Resource not found", statusCode = 404, cause, context) {
@@ -328,8 +328,12 @@ var _NetworkError = class _NetworkError extends SDKError {
328
328
  __name(_NetworkError, "NetworkError");
329
329
  var NetworkError = _NetworkError;
330
330
  var _TimeoutError = class _TimeoutError extends SDKError {
331
- constructor(message = "Request timed out", timeout, statusCode, cause) {
332
- super(message, statusCode, cause, { timeout });
331
+ /**
332
+ * @param context - Carried beside `timeout`. The mapper passes a `REQUEST_TIMEOUT` refusal's body
333
+ * as `context.response`, as it does for every other contract code.
334
+ */
335
+ constructor(message = "Request timed out", timeout, statusCode, cause, context) {
336
+ super(message, statusCode, cause, { ...context, timeout });
333
337
  __publicField(this, "code", "TIMEOUT_ERROR");
334
338
  __publicField(this, "retryable", true);
335
339
  }
@@ -355,8 +359,13 @@ var ConnectionError = _ConnectionError;
355
359
 
356
360
  // src/errors/validation-errors.ts
357
361
  var _BaseValidationError = class _BaseValidationError extends SDKError {
358
- constructor(message = "Validation failed", violations = [], statusCode = 400, cause) {
359
- super(message, statusCode, cause, { violations });
362
+ /**
363
+ * @param context - Carried beside `violations`. The mapper passes the refusal body as
364
+ * `context.response`, as it does for every other contract code, so a consumer that hands the
365
+ * service's own words on can read them here too.
366
+ */
367
+ constructor(message = "Validation failed", violations = [], statusCode = 400, cause, context) {
368
+ super(message, statusCode, cause, { ...context, violations });
360
369
  __publicField(this, "retryable", false);
361
370
  __publicField(this, "violations");
362
371
  this.violations = violations;
@@ -401,8 +410,8 @@ var _ValidationError = class _ValidationError extends BaseValidationError {
401
410
  __name(_ValidationError, "ValidationError");
402
411
  var ValidationError = _ValidationError;
403
412
  var _InvalidPayloadError = class _InvalidPayloadError extends BaseValidationError {
404
- constructor(message = "Invalid payload", violations = [], cause) {
405
- super(message, violations, 400, cause);
413
+ constructor(message = "Invalid payload", violations = [], cause, context) {
414
+ super(message, violations, 400, cause, context);
406
415
  __publicField(this, "code", "INVALID_PAYLOAD");
407
416
  }
408
417
  getUserMessage() {
@@ -591,10 +600,10 @@ var _ErrorMapper = class _ErrorMapper {
591
600
  );
592
601
  }
593
602
  case "REQUEST_INVALID": {
594
- return new InvalidPayloadError(message, violationsOf(refusal));
603
+ return new InvalidPayloadError(message, violationsOf(refusal), void 0, context);
595
604
  }
596
605
  case "CONTENT_INVALID": {
597
- return new ValidationError(message, violationsOf(refusal), 400);
606
+ return new ValidationError(message, violationsOf(refusal), 400, void 0, context);
598
607
  }
599
608
  case "METHOD_NOT_ALLOWED": {
600
609
  return new MethodNotAllowedError(message, context);
@@ -617,7 +626,7 @@ var _ErrorMapper = class _ErrorMapper {
617
626
  return new ResourceExpiredError(message, 410, void 0, context);
618
627
  }
619
628
  case "REQUEST_TIMEOUT": {
620
- return new TimeoutError(message, void 0, void 0, void 0);
629
+ return new TimeoutError(message, void 0, status, void 0, context);
621
630
  }
622
631
  case "PAYLOAD_TOO_LARGE": {
623
632
  return new PayloadTooLargeError(message, detailNumber(refusal, "limitBytes"), context);
@@ -768,6 +777,7 @@ var DECLARED_PATHS = {
768
777
  "/v1/meta/schema": true,
769
778
  "/v1/meta/schema/{contentType}": true,
770
779
  "/v1/meta/schema/{contentType}/fields": true,
780
+ "/v1/project": true,
771
781
  "/v1/query": true,
772
782
  "/v1/search": true,
773
783
  "/v1/search/autocomplete": true,
@@ -839,20 +849,29 @@ function readQuery(searchParams) {
839
849
  }
840
850
  __name(readQuery, "readQuery");
841
851
 
842
- // src/http/retry-policy.ts
843
- var IDEMPOTENT_METHODS = /* @__PURE__ */ new Set([
844
- "GET",
845
- "HEAD",
846
- "OPTIONS",
847
- "PUT",
848
- "DELETE",
849
- "TRACE"
850
- ]);
851
- function isReplaySafe(request) {
852
- if (IDEMPOTENT_METHODS.has(request.method.toUpperCase())) return true;
853
- return headerValue(request.headers, HEADER_IDEMPOTENCY_KEY.name) !== void 0;
852
+ // src/http/abortable-sleep.ts
853
+ function sleep(ms, signal) {
854
+ return new Promise((resolve) => {
855
+ if (isAborted(signal)) {
856
+ resolve();
857
+ return;
858
+ }
859
+ const done = /* @__PURE__ */ __name(() => {
860
+ clearTimeout(timer);
861
+ signal?.removeEventListener("abort", done);
862
+ resolve();
863
+ }, "done");
864
+ const timer = setTimeout(done, ms);
865
+ signal?.addEventListener("abort", done, { once: true });
866
+ });
854
867
  }
855
- __name(isReplaySafe, "isReplaySafe");
868
+ __name(sleep, "sleep");
869
+ function isAborted(signal) {
870
+ return signal?.aborted === true;
871
+ }
872
+ __name(isAborted, "isAborted");
873
+
874
+ // src/errors/instructed-delay.ts
856
875
  function serviceInstructedDelayMs(error, nowMs) {
857
876
  if (error instanceof ServiceIndeterminateError) {
858
877
  return error.retryAfterMs;
@@ -866,6 +885,21 @@ function serviceInstructedDelayMs(error, nowMs) {
866
885
  return wait > 0 ? wait : void 0;
867
886
  }
868
887
  __name(serviceInstructedDelayMs, "serviceInstructedDelayMs");
888
+
889
+ // src/http/retry-policy.ts
890
+ var IDEMPOTENT_METHODS = /* @__PURE__ */ new Set([
891
+ "GET",
892
+ "HEAD",
893
+ "OPTIONS",
894
+ "PUT",
895
+ "DELETE",
896
+ "TRACE"
897
+ ]);
898
+ function isReplaySafe(request) {
899
+ if (IDEMPOTENT_METHODS.has(request.method.toUpperCase())) return true;
900
+ return headerValue(request.headers, HEADER_IDEMPOTENCY_KEY.name) !== void 0;
901
+ }
902
+ __name(isReplaySafe, "isReplaySafe");
869
903
  var _DefaultRetryPolicy = class _DefaultRetryPolicy {
870
904
  constructor(config) {
871
905
  __publicField(this, "config");
@@ -902,9 +936,10 @@ var _HttpClient = class _HttpClient {
902
936
  this.timeout = config.timeout;
903
937
  this.debug = config.debug.enabled ?? false;
904
938
  this.logger = config.debug.logger;
939
+ const apiKey = presentKey(config.apiKey);
905
940
  this.defaultHeaders = {
906
941
  "Content-Type": "application/json",
907
- "X-API-Key": config.apiKey,
942
+ ...apiKey === void 0 ? {} : { [API_KEY_HEADER]: apiKey },
908
943
  ...config.headers
909
944
  };
910
945
  if (config.dataset != null) {
@@ -963,9 +998,21 @@ var _HttpClient = class _HttpClient {
963
998
  * The loop always makes one attempt. The incumbent's `while (attempt <
964
999
  * maxAttempts)` made none at `retryAttempts: 0` — a value the config validator
965
1000
  * accepts — and then dereferenced an unassigned `lastError`.
1001
+ *
1002
+ * A request that would carry no `X-API-Key` makes none: it rejects with
1003
+ * `MissingApiKeyError` before `fetch` is called, is not retried, and names
1004
+ * `apiKey`. This is where a client built without a key learns it — the
1005
+ * constructor accepts one so that a module-scope client can be evaluated by a
1006
+ * build that holds no key (REL-1120).
966
1007
  */
967
1008
  async request(config) {
968
1009
  const mergedConfig = this.mergeRequestConfig(config);
1010
+ if (presentKey(headerValue(mergedConfig.headers, API_KEY_HEADER)) === void 0) {
1011
+ throw new MissingApiKeyError(
1012
+ "`apiKey` is missing: this client was created without an API key, so the request was not sent. Pass `apiKey` to createClient \u2014 the documented setup reads it from RELYIO_API_KEY.",
1013
+ { url: mergedConfig.url, method: mergedConfig.method, sent: false }
1014
+ );
1015
+ }
969
1016
  this.log("debug", "Making request", {
970
1017
  method: mergedConfig.method,
971
1018
  url: mergedConfig.url
@@ -994,7 +1041,7 @@ var _HttpClient = class _HttpClient {
994
1041
  if (this.retryPolicy.shouldRetry(attempt, failure, mergedConfig)) {
995
1042
  const delay = this.retryPolicy.delayFor(attempt, failure);
996
1043
  this.log("debug", "Retrying request", { attempt, delay });
997
- await this.sleep(delay, config.signal);
1044
+ await sleep(delay, config.signal);
998
1045
  continue;
999
1046
  }
1000
1047
  const sdkError = ErrorMapper.mapGenericError(failure, {
@@ -1123,8 +1170,12 @@ var _HttpClient = class _HttpClient {
1123
1170
  headers: filteredHeaders,
1124
1171
  body: this.prepareRequestBody(config),
1125
1172
  signal: controller.signal,
1126
- // Include credentials for CORS requests if in browser and configured
1127
- ...typeof window !== "undefined" && this.config.browser.includeCredentials != null && { credentials: "include" }
1173
+ // Only where `window` exists. A server or edge runtime has no cookie jar for the mode to
1174
+ // govern, so the field is left off and the runtime's default stands. A Web Worker gets
1175
+ // that default too: it has cookies but no `window`.
1176
+ ...typeof window !== "undefined" && {
1177
+ credentials: credentialsModeFor(this.config.browser.includeCredentials)
1178
+ }
1128
1179
  };
1129
1180
  const send = this.config.fetch ?? globalThis.fetch;
1130
1181
  const response = await send(url, requestInit);
@@ -1280,22 +1331,6 @@ var _HttpClient = class _HttpClient {
1280
1331
  });
1281
1332
  return obj;
1282
1333
  }
1283
- /** A backoff the caller's abort cuts short; the next attempt then refuses to send. */
1284
- sleep(ms, signal) {
1285
- return new Promise((resolve) => {
1286
- if (isAborted(signal)) {
1287
- resolve();
1288
- return;
1289
- }
1290
- const done = /* @__PURE__ */ __name(() => {
1291
- clearTimeout(timer);
1292
- signal?.removeEventListener("abort", done);
1293
- resolve();
1294
- }, "done");
1295
- const timer = setTimeout(done, ms);
1296
- signal?.addEventListener("abort", done, { once: true });
1297
- });
1298
- }
1299
1334
  log(level, message, data) {
1300
1335
  if (!this.debug) return;
1301
1336
  if (this.logger != null) {
@@ -1307,10 +1342,23 @@ var _HttpClient = class _HttpClient {
1307
1342
  };
1308
1343
  __name(_HttpClient, "HttpClient");
1309
1344
  var HttpClient = _HttpClient;
1310
- function isAborted(signal) {
1311
- return signal?.aborted === true;
1345
+ var API_KEY_HEADER = "X-API-Key";
1346
+ function presentKey(value) {
1347
+ return typeof value === "string" && value.trim() !== "" ? value : void 0;
1312
1348
  }
1313
- __name(isAborted, "isAborted");
1349
+ __name(presentKey, "presentKey");
1350
+ function credentialsModeFor(includeCredentials) {
1351
+ switch (includeCredentials) {
1352
+ case false: {
1353
+ return "same-origin";
1354
+ }
1355
+ case true:
1356
+ case void 0: {
1357
+ return "include";
1358
+ }
1359
+ }
1360
+ }
1361
+ __name(credentialsModeFor, "credentialsModeFor");
1314
1362
 
1315
1363
  // src/content/query-builder.ts
1316
1364
  var _ContentQueryBuilder = class _ContentQueryBuilder {
@@ -1326,6 +1374,16 @@ var _ContentQueryBuilder = class _ContentQueryBuilder {
1326
1374
  __publicField(this, "childrenOptions");
1327
1375
  __publicField(this, "pageNumber");
1328
1376
  __publicField(this, "pageSizeValue");
1377
+ __publicField(this, "requestHeaders");
1378
+ }
1379
+ /**
1380
+ * Headers this query adds to the client's own, e.g. `X-RelyIO-Dataset` to list another dataset
1381
+ * the key can see. A name the client already sends is replaced for this query only; the client
1382
+ * is not changed.
1383
+ */
1384
+ headers(headers) {
1385
+ this.requestHeaders = headers;
1386
+ return this;
1329
1387
  }
1330
1388
  /**
1331
1389
  * Add where conditions to the query
@@ -1523,7 +1581,11 @@ var _ContentQueryBuilder = class _ContentQueryBuilder {
1523
1581
  const response = await this.httpClient.send(
1524
1582
  "/v1/{contentType}",
1525
1583
  "get",
1526
- { path: { contentType: this.contentType }, query: this.buildQueryParams() }
1584
+ {
1585
+ path: { contentType: this.contentType },
1586
+ query: this.buildQueryParams(),
1587
+ headers: this.requestHeaders
1588
+ }
1527
1589
  );
1528
1590
  return response.body;
1529
1591
  }
@@ -1591,6 +1653,7 @@ var _ContentQueryBuilder = class _ContentQueryBuilder {
1591
1653
  cloned.fieldsProjection = this.fieldsProjection ? [...this.fieldsProjection] : void 0;
1592
1654
  cloned.pageNumber = this.pageNumber;
1593
1655
  cloned.pageSizeValue = this.pageSizeValue;
1656
+ cloned.requestHeaders = this.requestHeaders ? { ...this.requestHeaders } : void 0;
1594
1657
  return cloned;
1595
1658
  }
1596
1659
  /**
@@ -1698,6 +1761,7 @@ var _ContentByIdBuilder = class _ContentByIdBuilder {
1698
1761
  __publicField(this, "localesArray");
1699
1762
  __publicField(this, "fetchAllLocales", false);
1700
1763
  __publicField(this, "childrenOptions");
1764
+ __publicField(this, "requestHeaders");
1701
1765
  }
1702
1766
  /**
1703
1767
  * Include related content in the response
@@ -1728,6 +1792,15 @@ var _ContentByIdBuilder = class _ContentByIdBuilder {
1728
1792
  this.fetchAllLocales = false;
1729
1793
  return this;
1730
1794
  }
1795
+ /**
1796
+ * Headers this one read adds to the client's own, e.g. `X-RelyIO-Dataset` to read another dataset
1797
+ * the key can see. A name the client already sends is replaced for this request only; the client
1798
+ * is not changed.
1799
+ */
1800
+ headers(headers) {
1801
+ this.requestHeaders = headers;
1802
+ return this;
1803
+ }
1731
1804
  /**
1732
1805
  * Fetch content in all available locales
1733
1806
  * Returns default locale at top level, others in _metadata.localizations
@@ -1781,7 +1854,11 @@ var _ContentByIdBuilder = class _ContentByIdBuilder {
1781
1854
  return await this.httpClient.fetchPayload(
1782
1855
  "/v1/{contentType}/{id}",
1783
1856
  "get",
1784
- { path: { contentType: this.contentType, id: this.id }, query: params }
1857
+ {
1858
+ path: { contentType: this.contentType, id: this.id },
1859
+ query: params,
1860
+ headers: this.requestHeaders
1861
+ }
1785
1862
  );
1786
1863
  } catch (error) {
1787
1864
  if (isResourceNotFound(error)) return null;
@@ -1811,6 +1888,14 @@ function writeHeaders(options) {
1811
1888
  return Object.keys(headers).length > 0 ? headers : void 0;
1812
1889
  }
1813
1890
  __name(writeHeaders, "writeHeaders");
1891
+ function requestHeaders(headers, typed) {
1892
+ if (headers === void 0) return typed;
1893
+ if (typed === void 0) return headers;
1894
+ const typedNames = new Set(Object.keys(typed).map((name) => name.toLowerCase()));
1895
+ const own = Object.entries(headers).filter(([name]) => !typedNames.has(name.toLowerCase()));
1896
+ return { ...Object.fromEntries(own), ...typed };
1897
+ }
1898
+ __name(requestHeaders, "requestHeaders");
1814
1899
  function entityTagOf(response) {
1815
1900
  return headerValue(response.headers, HEADER_ENTITY_TAG.name);
1816
1901
  }
@@ -1880,7 +1965,8 @@ var _ContentClient = class _ContentClient {
1880
1965
  /**
1881
1966
  * Fetch a single content item by ID
1882
1967
  * @param id - Entry ID or slug
1883
- * @param options - Fetch options (include, locale)
1968
+ * @param options - Fetch options (include, locale, and `headers` this one request adds — e.g.
1969
+ * `{ 'X-RelyIO-Dataset': 'staging' }` to read a dataset other than the client's)
1884
1970
  */
1885
1971
  async fetchOne(id, options) {
1886
1972
  const builder = this.findById(id);
@@ -1890,6 +1976,9 @@ var _ContentClient = class _ContentClient {
1890
1976
  if (options?.locale != null && options.locale !== "") {
1891
1977
  builder.locale(options.locale);
1892
1978
  }
1979
+ if (options?.headers != null) {
1980
+ builder.headers(options.headers);
1981
+ }
1893
1982
  return builder.execute();
1894
1983
  }
1895
1984
  /**
@@ -1904,6 +1993,11 @@ var _ContentClient = class _ContentClient {
1904
1993
  * `entityTag` is `undefined` when the response came from the cache: a cached
1905
1994
  * body cannot know the current version, and an absent tag beats a stale one
1906
1995
  * that would refuse the next write.
1996
+ *
1997
+ * Unlike {@link fetchOne}, a missing entry is not `null`: the service's
1998
+ * `RESOURCE_NOT_FOUND` is thrown as it arrived, with the `kind` that says
1999
+ * whether the entry, the content type or the schema was missing. `headers`
2000
+ * are the ones this read adds, as for `fetchOne`.
1907
2001
  */
1908
2002
  async fetchOneWithTag(id, options) {
1909
2003
  const query = {};
@@ -1913,7 +2007,7 @@ var _ContentClient = class _ContentClient {
1913
2007
  const response = await this.httpClient.send(
1914
2008
  "/v1/{contentType}/{id}",
1915
2009
  "get",
1916
- { path: { contentType: this.contentType, id }, query }
2010
+ { path: { contentType: this.contentType, id }, query, headers: options?.headers }
1917
2011
  );
1918
2012
  return { entry: readPayload(response.body), entityTag: entityTagOf(response) };
1919
2013
  }
@@ -1935,17 +2029,20 @@ var _ContentClient = class _ContentClient {
1935
2029
  /**
1936
2030
  * Create a new content item
1937
2031
  * @param data - Content data
1938
- * @param options - Create options (slug, locale, parentId, sortOrder)
2032
+ * @param options - Create options (slug, locale, parentId, sortOrder), and `headers` this one
2033
+ * request adds — e.g. `{ 'X-RelyIO-Dataset': 'staging' }` to write a dataset other than the
2034
+ * client's. They are sent, never put in the body; `idempotencyKey` still wins over an
2035
+ * `Idempotency-Key` among them.
1939
2036
  */
1940
2037
  async create(data, options) {
1941
- const { idempotencyKey, ...body } = options ?? {};
2038
+ const { idempotencyKey, headers, ...body } = options ?? {};
1942
2039
  return this.httpClient.fetchPayload(
1943
2040
  "/v1/{contentType}",
1944
2041
  "post",
1945
2042
  {
1946
2043
  path: { contentType: this.contentType },
1947
2044
  body: { content: data, ...body },
1948
- headers: writeHeaders({ idempotencyKey })
2045
+ headers: requestHeaders(headers, writeHeaders({ idempotencyKey }))
1949
2046
  }
1950
2047
  );
1951
2048
  }
@@ -1953,17 +2050,19 @@ var _ContentClient = class _ContentClient {
1953
2050
  * Update a content item by ID (partial update)
1954
2051
  * @param id - Entry ID
1955
2052
  * @param data - Fields to update
1956
- * @param options - Update options (slug, locale, parentId, sortOrder)
2053
+ * @param options - Update options (slug, locale, parentId, sortOrder), and `headers` this one
2054
+ * request adds, as for {@link create}. `ifMatch` and `idempotencyKey` win over the same
2055
+ * header among them.
1957
2056
  */
1958
2057
  async update(id, data, options) {
1959
- const { idempotencyKey, ifMatch, ...body } = options ?? {};
2058
+ const { idempotencyKey, ifMatch, headers, ...body } = options ?? {};
1960
2059
  return this.httpClient.fetchPayload(
1961
2060
  "/v1/{contentType}/{id}",
1962
2061
  "patch",
1963
2062
  {
1964
2063
  path: { contentType: this.contentType, id },
1965
2064
  body: { content: data, ...body },
1966
- headers: writeHeaders({ idempotencyKey, ifMatch })
2065
+ headers: requestHeaders(headers, writeHeaders({ idempotencyKey, ifMatch }))
1967
2066
  }
1968
2067
  );
1969
2068
  }
@@ -2058,8 +2157,9 @@ var _ContentClient = class _ContentClient {
2058
2157
  if (options?.include != null) {
2059
2158
  params.include = options.include.join(",");
2060
2159
  }
2061
- if (options?.locale != null && options.locale !== "") {
2062
- params.locale = options.locale;
2160
+ const locale = options?.locale ?? this.defaultLocale;
2161
+ if (locale != null && locale !== "") {
2162
+ params.locale = locale;
2063
2163
  }
2064
2164
  return this.httpClient.fetchPayload(
2065
2165
  "/v1/{contentType}/{id}/children",
@@ -2717,9 +2817,10 @@ __name(isAssetEntry, "isAssetEntry");
2717
2817
 
2718
2818
  // src/content/singleton-client.ts
2719
2819
  var _SingletonClient = class _SingletonClient {
2720
- constructor(httpClient, singletonKey) {
2820
+ constructor(httpClient, singletonKey, defaultLocale) {
2721
2821
  this.httpClient = httpClient;
2722
2822
  this.singletonKey = singletonKey;
2823
+ this.defaultLocale = defaultLocale;
2723
2824
  __publicField(this, "includeFields");
2724
2825
  __publicField(this, "localeValue");
2725
2826
  }
@@ -2744,11 +2845,6 @@ var _SingletonClient = class _SingletonClient {
2744
2845
  * @param options - Optional fetch options (alternative to fluent methods)
2745
2846
  */
2746
2847
  async fetch(options) {
2747
- const includeValue = options?.include ?? this.includeFields;
2748
- options?.locale ?? this.localeValue;
2749
- if (includeValue) {
2750
- includeValue.join(",");
2751
- }
2752
2848
  try {
2753
2849
  return (await this.fetchWithTag(options)).content;
2754
2850
  } catch (error) {
@@ -2766,7 +2862,7 @@ var _SingletonClient = class _SingletonClient {
2766
2862
  async fetchWithTag(options) {
2767
2863
  const query = {};
2768
2864
  const includeValue = options?.include ?? this.includeFields;
2769
- const localeValueFinal = options?.locale ?? this.localeValue;
2865
+ const localeValueFinal = options?.locale ?? this.localeValue ?? this.defaultLocale;
2770
2866
  if (includeValue) query.include = includeValue.join(",");
2771
2867
  if (localeValueFinal != null && localeValueFinal !== "") query.locale = localeValueFinal;
2772
2868
  const response = await this.httpClient.send(
@@ -2863,9 +2959,10 @@ var SingletonClient = _SingletonClient;
2863
2959
 
2864
2960
  // src/content/settings-client.ts
2865
2961
  var _SettingsClient = class _SettingsClient {
2866
- constructor(httpClient, groupId) {
2962
+ constructor(httpClient, groupId, defaultLocale) {
2867
2963
  this.httpClient = httpClient;
2868
2964
  this.groupId = groupId;
2965
+ this.defaultLocale = defaultLocale;
2869
2966
  __publicField(this, "includeFields");
2870
2967
  __publicField(this, "localeValue");
2871
2968
  }
@@ -2890,11 +2987,6 @@ var _SettingsClient = class _SettingsClient {
2890
2987
  * @param options - Optional fetch options (alternative to fluent methods)
2891
2988
  */
2892
2989
  async fetch(options) {
2893
- const includeValue = options?.include ?? this.includeFields;
2894
- options?.locale ?? this.localeValue;
2895
- if (includeValue) {
2896
- includeValue.join(",");
2897
- }
2898
2990
  try {
2899
2991
  return (await this.fetchWithTag(options)).settings;
2900
2992
  } catch (error) {
@@ -2924,7 +3016,7 @@ var _SettingsClient = class _SettingsClient {
2924
3016
  async fetchWithTag(options) {
2925
3017
  const query = {};
2926
3018
  const includeValue = options?.include ?? this.includeFields;
2927
- const localeValueFinal = options?.locale ?? this.localeValue;
3019
+ const localeValueFinal = options?.locale ?? this.localeValue ?? this.defaultLocale;
2928
3020
  if (includeValue) query.include = includeValue.join(",");
2929
3021
  if (localeValueFinal != null && localeValueFinal !== "") query.locale = localeValueFinal;
2930
3022
  const response = await this.httpClient.send(
@@ -3026,14 +3118,28 @@ var SettingsClient = _SettingsClient;
3026
3118
 
3027
3119
  // src/search/search-client.ts
3028
3120
  var _SearchBuilder = class _SearchBuilder {
3029
- constructor(httpClient, query) {
3121
+ /**
3122
+ * @param defaultLocale - The client's locale (`withLocale()`), sent when `.locale()` names none
3123
+ */
3124
+ constructor(httpClient, query, defaultLocale) {
3030
3125
  this.httpClient = httpClient;
3126
+ this.defaultLocale = defaultLocale;
3031
3127
  __publicField(this, "query");
3032
3128
  __publicField(this, "searchOptions", {});
3129
+ __publicField(this, "requestHeaders");
3033
3130
  this.query = query;
3034
3131
  }
3035
3132
  /**
3036
- * Search within a single content type
3133
+ * Headers this search adds to the client's own, e.g. `X-RelyIO-Dataset` to search another
3134
+ * dataset the key can see. A name the client already sends is replaced for this search only.
3135
+ */
3136
+ headers(headers) {
3137
+ this.requestHeaders = headers;
3138
+ return this;
3139
+ }
3140
+ /**
3141
+ * Search within a single content type. Where a schema types the client, the name is one of its
3142
+ * content types and the results are that type's entries.
3037
3143
  * @param contentType - Content type to search in
3038
3144
  */
3039
3145
  in(contentType) {
@@ -3042,7 +3148,8 @@ var _SearchBuilder = class _SearchBuilder {
3042
3148
  return this;
3043
3149
  }
3044
3150
  /**
3045
- * Search across multiple content types
3151
+ * Search across multiple content types. Where a schema types the client, the names are its content
3152
+ * types and the results are a union of their entries — every type's, for `'*'`.
3046
3153
  * @param contentTypes - Array of content types or '*' for all
3047
3154
  */
3048
3155
  across(contentTypes) {
@@ -3077,6 +3184,16 @@ var _SearchBuilder = class _SearchBuilder {
3077
3184
  this.searchOptions.fields = projection;
3078
3185
  return this;
3079
3186
  }
3187
+ /**
3188
+ * Order the results by a field instead of by relevance. Called again, it adds a tie-breaker.
3189
+ * `_score` names relevance itself.
3190
+ * @param field - Field path to sort by (e.g. `updatedAt`, `pricing.basePrice`)
3191
+ * @param direction - `asc` or `desc` (the route's default is `desc`)
3192
+ */
3193
+ sort(field, direction = "desc") {
3194
+ this.searchOptions.sort = [...this.searchOptions.sort ?? [], { field, direction }];
3195
+ return this;
3196
+ }
3080
3197
  /**
3081
3198
  * Limit number of results
3082
3199
  * @param count - Maximum results
@@ -3137,6 +3254,9 @@ var _SearchBuilder = class _SearchBuilder {
3137
3254
  if (this.searchOptions.fields) {
3138
3255
  body.fields = this.searchOptions.fields;
3139
3256
  }
3257
+ if (this.searchOptions.sort) {
3258
+ body.sort = this.searchOptions.sort;
3259
+ }
3140
3260
  if (this.searchOptions.limit !== void 0) {
3141
3261
  body.limit = this.searchOptions.limit;
3142
3262
  }
@@ -3149,13 +3269,14 @@ var _SearchBuilder = class _SearchBuilder {
3149
3269
  if (this.searchOptions.explain === true) {
3150
3270
  body.explain = true;
3151
3271
  }
3152
- if (this.searchOptions.locale != null && this.searchOptions.locale !== "") {
3153
- body.locale = this.searchOptions.locale;
3272
+ const locale = this.searchOptions.locale ?? this.defaultLocale;
3273
+ if (locale != null && locale !== "") {
3274
+ body.locale = locale;
3154
3275
  }
3155
3276
  const response = await this.httpClient.send(
3156
3277
  "/v1/search",
3157
3278
  "post",
3158
- { body }
3279
+ { body, headers: this.requestHeaders }
3159
3280
  );
3160
3281
  return response.body;
3161
3282
  }
@@ -3175,8 +3296,12 @@ var _SearchBuilder = class _SearchBuilder {
3175
3296
  __name(_SearchBuilder, "SearchBuilder");
3176
3297
  var SearchBuilder = _SearchBuilder;
3177
3298
  var _AutocompleteBuilder = class _AutocompleteBuilder {
3178
- constructor(httpClient, query) {
3299
+ /**
3300
+ * @param defaultLocale - The client's locale (`withLocale()`), sent when `.locale()` names none
3301
+ */
3302
+ constructor(httpClient, query, defaultLocale) {
3179
3303
  this.httpClient = httpClient;
3304
+ this.defaultLocale = defaultLocale;
3180
3305
  __publicField(this, "query");
3181
3306
  __publicField(this, "autocompleteOptions", {});
3182
3307
  this.query = query;
@@ -3255,8 +3380,9 @@ var _AutocompleteBuilder = class _AutocompleteBuilder {
3255
3380
  if (this.autocompleteOptions.limit !== void 0) {
3256
3381
  body.limit = this.autocompleteOptions.limit;
3257
3382
  }
3258
- if (this.autocompleteOptions.locale != null && this.autocompleteOptions.locale !== "") {
3259
- body.locale = this.autocompleteOptions.locale;
3383
+ const locale = this.autocompleteOptions.locale ?? this.defaultLocale;
3384
+ if (locale != null && locale !== "") {
3385
+ body.locale = locale;
3260
3386
  }
3261
3387
  const response = await this.httpClient.send("/v1/search/autocomplete", "post", { body });
3262
3388
  return response.body;
@@ -3271,18 +3397,41 @@ var _AutocompleteBuilder = class _AutocompleteBuilder {
3271
3397
  __name(_AutocompleteBuilder, "AutocompleteBuilder");
3272
3398
  var AutocompleteBuilder = _AutocompleteBuilder;
3273
3399
  var _SimilarBuilder = class _SimilarBuilder {
3274
- constructor(httpClient, sourceId) {
3400
+ /**
3401
+ * @param defaultLocale - The client's locale (`withLocale()`), sent when `.locale()` names none
3402
+ */
3403
+ constructor(httpClient, sourceId, defaultLocale) {
3275
3404
  this.httpClient = httpClient;
3405
+ this.defaultLocale = defaultLocale;
3276
3406
  __publicField(this, "sourceId");
3277
3407
  __publicField(this, "similarOptions", {});
3408
+ __publicField(this, "requestHeaders");
3278
3409
  this.sourceId = sourceId;
3279
3410
  }
3411
+ /**
3412
+ * Headers this read adds to the client's own, e.g. `X-RelyIO-Dataset` to read another dataset
3413
+ * the key can see. A name the client already sends is replaced for this read only.
3414
+ */
3415
+ headers(headers) {
3416
+ this.requestHeaders = headers;
3417
+ return this;
3418
+ }
3280
3419
  /**
3281
3420
  * Find similar content within a content type
3282
3421
  * @param contentType - Content type to search
3283
3422
  */
3284
3423
  in(contentType) {
3285
3424
  this.similarOptions.contentType = contentType;
3425
+ this.similarOptions.contentTypes = void 0;
3426
+ return this;
3427
+ }
3428
+ /**
3429
+ * Find similar content across several content types, or `'*'` for every type
3430
+ * @param contentTypes - Array of content types or '*' for all
3431
+ */
3432
+ across(contentTypes) {
3433
+ this.similarOptions.contentTypes = contentTypes;
3434
+ this.similarOptions.contentType = void 0;
3286
3435
  return this;
3287
3436
  }
3288
3437
  /**
@@ -3316,26 +3465,33 @@ var _SimilarBuilder = class _SimilarBuilder {
3316
3465
  * Execute the similar query
3317
3466
  */
3318
3467
  async execute() {
3319
- if (this.similarOptions.contentType == null || this.similarOptions.contentType === "") {
3320
- throw new Error('Content type is required for similar search. Use .in("contentType")');
3468
+ const { contentType, contentTypes } = this.similarOptions;
3469
+ if ((contentType == null || contentType === "") && contentTypes == null) {
3470
+ throw new Error(
3471
+ 'Content type is required for similar search. Use .in("contentType") or .across("*")'
3472
+ );
3473
+ }
3474
+ const body = { source: this.sourceId };
3475
+ if (contentType != null && contentType !== "") {
3476
+ body.contentType = contentType;
3477
+ }
3478
+ if (contentTypes != null) {
3479
+ body.contentTypes = contentTypes;
3321
3480
  }
3322
- const body = {
3323
- source: this.sourceId,
3324
- contentType: this.similarOptions.contentType
3325
- };
3326
3481
  if (this.similarOptions.where && Object.keys(this.similarOptions.where).length > 0) {
3327
3482
  body.where = this.similarOptions.where;
3328
3483
  }
3329
3484
  if (this.similarOptions.limit !== void 0) {
3330
3485
  body.limit = this.similarOptions.limit;
3331
3486
  }
3332
- if (this.similarOptions.locale != null && this.similarOptions.locale !== "") {
3333
- body.locale = this.similarOptions.locale;
3487
+ const locale = this.similarOptions.locale ?? this.defaultLocale;
3488
+ if (locale != null && locale !== "") {
3489
+ body.locale = locale;
3334
3490
  }
3335
3491
  const response = await this.httpClient.send(
3336
3492
  "/v1/search/similar",
3337
3493
  "post",
3338
- { body }
3494
+ { body, headers: this.requestHeaders }
3339
3495
  );
3340
3496
  return response.body;
3341
3497
  }
@@ -3355,29 +3511,34 @@ var _SimilarBuilder = class _SimilarBuilder {
3355
3511
  __name(_SimilarBuilder, "SimilarBuilder");
3356
3512
  var SimilarBuilder = _SimilarBuilder;
3357
3513
  var _SearchClient = class _SearchClient {
3358
- constructor(httpClient) {
3514
+ /**
3515
+ * @param defaultLocale - The client's locale (`withLocale()`): every builder here sends it when
3516
+ * the query names none, as `content()`'s reads do
3517
+ */
3518
+ constructor(httpClient, defaultLocale) {
3359
3519
  this.httpClient = httpClient;
3520
+ this.defaultLocale = defaultLocale;
3360
3521
  }
3361
3522
  /**
3362
3523
  * Start a search query
3363
3524
  * @param query - Search query string
3364
3525
  */
3365
3526
  search(query) {
3366
- return new SearchBuilder(this.httpClient, query);
3527
+ return new SearchBuilder(this.httpClient, query, this.defaultLocale);
3367
3528
  }
3368
3529
  /**
3369
3530
  * Start an autocomplete query
3370
3531
  * @param query - Partial query to autocomplete
3371
3532
  */
3372
3533
  autocomplete(query) {
3373
- return new AutocompleteBuilder(this.httpClient, query);
3534
+ return new AutocompleteBuilder(this.httpClient, query, this.defaultLocale);
3374
3535
  }
3375
3536
  /**
3376
3537
  * Find content similar to a given entry
3377
3538
  * @param sourceId - Entry ID to find similar content for
3378
3539
  */
3379
3540
  similar(sourceId) {
3380
- return new SimilarBuilder(this.httpClient, sourceId);
3541
+ return new SimilarBuilder(this.httpClient, sourceId, this.defaultLocale);
3381
3542
  }
3382
3543
  /**
3383
3544
  * Quick search helper - searches all content types
@@ -3402,6 +3563,7 @@ var _BatchBuilder = class _BatchBuilder {
3402
3563
  returnData: true,
3403
3564
  continueOnError: true
3404
3565
  });
3566
+ __publicField(this, "requestHeaders");
3405
3567
  }
3406
3568
  // ==========================================================================
3407
3569
  // ENTRY OPERATIONS
@@ -3603,6 +3765,15 @@ var _BatchBuilder = class _BatchBuilder {
3603
3765
  this.defaultOptions.continueOnError = false;
3604
3766
  return this;
3605
3767
  }
3768
+ /**
3769
+ * Headers the batch request adds — e.g. `{ 'X-RelyIO-Dataset': 'staging' }` to write a dataset
3770
+ * other than the client's. They replace the client's own header of the same name for this
3771
+ * request alone, and apply to every operation in it: a batch is one request.
3772
+ */
3773
+ headers(headers) {
3774
+ this.requestHeaders = { ...this.requestHeaders, ...headers };
3775
+ return this;
3776
+ }
3606
3777
  // ==========================================================================
3607
3778
  // EXECUTION
3608
3779
  // ==========================================================================
@@ -3632,6 +3803,7 @@ var _BatchBuilder = class _BatchBuilder {
3632
3803
  const cloned = new _BatchBuilder(this.httpClient);
3633
3804
  cloned.operations = [...this.operations];
3634
3805
  cloned.defaultOptions = { ...this.defaultOptions };
3806
+ cloned.requestHeaders = this.requestHeaders === void 0 ? void 0 : { ...this.requestHeaders };
3635
3807
  return cloned;
3636
3808
  }
3637
3809
  /**
@@ -3649,7 +3821,8 @@ var _BatchBuilder = class _BatchBuilder {
3649
3821
  }
3650
3822
  const finalOptions = { ...this.defaultOptions, ...options };
3651
3823
  return this.httpClient.fetchPayload("/v1/batch", "post", {
3652
- body: { operations: this.operations, options: finalOptions }
3824
+ body: { operations: this.operations, options: finalOptions },
3825
+ headers: this.requestHeaders
3653
3826
  });
3654
3827
  }
3655
3828
  /**
@@ -3729,9 +3902,21 @@ __name(_BatchClient, "BatchClient");
3729
3902
  var BatchClient = _BatchClient;
3730
3903
 
3731
3904
  // src/queries/adhoc-query-client.ts
3905
+ function withClientLocale(parameters, clientLocale) {
3906
+ if (clientLocale === void 0 || clientLocale === "" || parameters?.locale != null) {
3907
+ return parameters;
3908
+ }
3909
+ return { ...parameters, locale: clientLocale };
3910
+ }
3911
+ __name(withClientLocale, "withClientLocale");
3732
3912
  var _AdhocQueryBuilder = class _AdhocQueryBuilder {
3733
- constructor(httpClient) {
3913
+ /**
3914
+ * @param defaultLocale - The client's locale (`withLocale()`), sent as the `locale` parameter
3915
+ * when `.param('locale', …)` binds none
3916
+ */
3917
+ constructor(httpClient, defaultLocale) {
3734
3918
  this.httpClient = httpClient;
3919
+ this.defaultLocale = defaultLocale;
3735
3920
  __publicField(this, "queryDef", {});
3736
3921
  __publicField(this, "parameters", {});
3737
3922
  __publicField(this, "options", {});
@@ -3971,8 +4156,9 @@ var _AdhocQueryBuilder = class _AdhocQueryBuilder {
3971
4156
  const body = {
3972
4157
  query: this.queryDef
3973
4158
  };
3974
- if (Object.keys(this.parameters).length > 0) {
3975
- body.parameters = this.parameters;
4159
+ const parameters = withClientLocale(this.parameters, this.defaultLocale);
4160
+ if (parameters !== void 0 && Object.keys(parameters).length > 0) {
4161
+ body.parameters = parameters;
3976
4162
  }
3977
4163
  if (Object.keys(this.options).length > 0) {
3978
4164
  body.options = this.options;
@@ -4003,7 +4189,7 @@ var _AdhocQueryBuilder = class _AdhocQueryBuilder {
4003
4189
  * Clone the builder
4004
4190
  */
4005
4191
  clone() {
4006
- const cloned = new _AdhocQueryBuilder(this.httpClient);
4192
+ const cloned = new _AdhocQueryBuilder(this.httpClient, this.defaultLocale);
4007
4193
  cloned.queryDef = JSON.parse(JSON.stringify(this.queryDef));
4008
4194
  cloned.parameters = { ...this.parameters };
4009
4195
  cloned.options = { ...this.options };
@@ -4013,27 +4199,36 @@ var _AdhocQueryBuilder = class _AdhocQueryBuilder {
4013
4199
  __name(_AdhocQueryBuilder, "AdhocQueryBuilder");
4014
4200
  var AdhocQueryBuilder = _AdhocQueryBuilder;
4015
4201
  var _AdhocQueryClient = class _AdhocQueryClient {
4016
- constructor(httpClient) {
4202
+ /**
4203
+ * @param defaultLocale - The client's locale (`withLocale()`), sent as the `locale` parameter of
4204
+ * every query that binds none
4205
+ */
4206
+ constructor(httpClient, defaultLocale) {
4017
4207
  this.httpClient = httpClient;
4208
+ this.defaultLocale = defaultLocale;
4018
4209
  }
4019
4210
  /**
4020
4211
  * Create a new query builder
4021
4212
  */
4022
4213
  build() {
4023
- return new AdhocQueryBuilder(this.httpClient);
4214
+ return new AdhocQueryBuilder(this.httpClient, this.defaultLocale);
4024
4215
  }
4025
4216
  /**
4026
4217
  * Execute a query definition directly
4027
4218
  * @param definition - Query definition
4028
4219
  * @param parameters - Parameter values
4029
4220
  * @param options - Execution options
4221
+ * @param request - `headers` this one request adds to the client's own, e.g. `X-RelyIO-Dataset`
4222
+ * to query another dataset the key can see. A name the client already sends is replaced for
4223
+ * this request only.
4030
4224
  */
4031
- async execute(definition, parameters, options) {
4225
+ async execute(definition, parameters, options, request) {
4032
4226
  const body = {
4033
4227
  query: definition
4034
4228
  };
4035
- if (parameters && Object.keys(parameters).length > 0) {
4036
- body.parameters = parameters;
4229
+ const sent = withClientLocale(parameters, this.defaultLocale);
4230
+ if (sent !== void 0 && Object.keys(sent).length > 0) {
4231
+ body.parameters = sent;
4037
4232
  }
4038
4233
  if (options && Object.keys(options).length > 0) {
4039
4234
  body.options = options;
@@ -4041,7 +4236,7 @@ var _AdhocQueryClient = class _AdhocQueryClient {
4041
4236
  return this.httpClient.fetchPayload(
4042
4237
  "/v1/query",
4043
4238
  "post",
4044
- { body }
4239
+ { body, headers: request?.headers }
4045
4240
  );
4046
4241
  }
4047
4242
  /**
@@ -4105,12 +4300,12 @@ async function* pollChanges(http, options = {}) {
4105
4300
  let since = options.since;
4106
4301
  let resynced;
4107
4302
  for (; ; ) {
4108
- if (aborted(signal)) return;
4303
+ if (isAborted(signal)) return;
4109
4304
  let response;
4110
4305
  try {
4111
4306
  response = await pollOnce(http, since, options);
4112
4307
  } catch (error) {
4113
- if (aborted(signal)) return;
4308
+ if (isAborted(signal)) return;
4114
4309
  if (!(error instanceof ResourceExpiredError) || since === void 0) throw error;
4115
4310
  const expired = ChangesExpiredError.of(error);
4116
4311
  const onExpired = options.onExpired ?? "throw";
@@ -4128,11 +4323,11 @@ async function* pollChanges(http, options = {}) {
4128
4323
  const expired = resynced;
4129
4324
  resynced = void 0;
4130
4325
  await options.onExpired(expired);
4131
- if (aborted(signal)) return;
4326
+ if (isAborted(signal)) return;
4132
4327
  }
4133
4328
  for (const change of data) {
4134
4329
  yield change;
4135
- if (aborted(signal)) return;
4330
+ if (isAborted(signal)) return;
4136
4331
  }
4137
4332
  if (data.length === 0) {
4138
4333
  const wait = parseRetryAfterMs(headerValue(response.headers, "Retry-After"), Date.now());
@@ -4141,6 +4336,21 @@ async function* pollChanges(http, options = {}) {
4141
4336
  }
4142
4337
  }
4143
4338
  __name(pollChanges, "pollChanges");
4339
+ async function readChangesHead(http, options = {}) {
4340
+ const response = await http.send("/v1/changes", "get", {
4341
+ query: {},
4342
+ ...options.signal === void 0 ? {} : { signal: options.signal }
4343
+ });
4344
+ const { data, page } = response.body;
4345
+ if (page.nextCursor === null) {
4346
+ throw new Error("GET /v1/changes answered without page.nextCursor");
4347
+ }
4348
+ if (data.length > 0) {
4349
+ throw new Error("GET /v1/changes without since answered changes; a head carries none");
4350
+ }
4351
+ return page.nextCursor;
4352
+ }
4353
+ __name(readChangesHead, "readChangesHead");
4144
4354
  async function pollOnce(http, since, options) {
4145
4355
  const query = {};
4146
4356
  if (since !== void 0) query.since = since;
@@ -4154,28 +4364,12 @@ async function pollOnce(http, since, options) {
4154
4364
  });
4155
4365
  }
4156
4366
  __name(pollOnce, "pollOnce");
4157
- function sleep(ms, signal) {
4158
- return new Promise((resolve) => {
4159
- if (aborted(signal)) {
4160
- resolve();
4161
- return;
4162
- }
4163
- const done = /* @__PURE__ */ __name(() => {
4164
- clearTimeout(timer);
4165
- signal?.removeEventListener("abort", done);
4166
- resolve();
4167
- }, "done");
4168
- const timer = setTimeout(done, ms);
4169
- signal?.addEventListener("abort", done, { once: true });
4170
- });
4171
- }
4172
- __name(sleep, "sleep");
4173
- function aborted(signal) {
4174
- return signal?.aborted === true;
4175
- }
4176
- __name(aborted, "aborted");
4177
4367
 
4178
4368
  // src/config/dev-detection.ts
4369
+ function sandboxStateOf(resolution) {
4370
+ return resolution.state === "active" ? resolution.sandbox : resolution;
4371
+ }
4372
+ __name(sandboxStateOf, "sandboxStateOf");
4179
4373
  function getProcess() {
4180
4374
  return typeof process !== "undefined" ? process : void 0;
4181
4375
  }
@@ -4199,11 +4393,14 @@ function resolveFromEnv(proc, now) {
4199
4393
  case "active":
4200
4394
  return {
4201
4395
  state: "active",
4202
- override: {
4203
- dataset: verdict.credentials.dataset,
4204
- apiKey: verdict.credentials.apiKey,
4396
+ sandbox: {
4397
+ state: "active",
4398
+ datasetId: verdict.credentials.dataset,
4399
+ branchId: null,
4400
+ expiresAt: verdict.expiresAt,
4205
4401
  source: "env"
4206
- }
4402
+ },
4403
+ apiKey: verdict.credentials.apiKey
4207
4404
  };
4208
4405
  case "expired":
4209
4406
  return { state: "expired", expiredAt: verdict.expiredAt };
@@ -4234,11 +4431,14 @@ function resolveFromFile(proc, now) {
4234
4431
  case "active":
4235
4432
  return {
4236
4433
  state: "active",
4237
- override: {
4238
- dataset: verdict.context.datasetId,
4239
- apiKey: verdict.context.previewApiKey,
4434
+ sandbox: {
4435
+ state: "active",
4436
+ datasetId: verdict.context.datasetId,
4437
+ branchId: verdict.context.branchId,
4438
+ expiresAt: verdict.context.expiresAt,
4240
4439
  source: "file"
4241
- }
4440
+ },
4441
+ apiKey: verdict.context.previewApiKey
4242
4442
  };
4243
4443
  case "expired":
4244
4444
  return { state: "expired", expiredAt: verdict.expiredAt };
@@ -4301,10 +4501,7 @@ var DEFAULT_CONFIG = {
4301
4501
  }
4302
4502
  };
4303
4503
  function validateConfig(config) {
4304
- if (!config.apiKey || config.apiKey.length === 0) {
4305
- throw new Error("API key is required");
4306
- }
4307
- if (typeof config.apiKey !== "string") {
4504
+ if (config.apiKey != null && typeof config.apiKey !== "string") {
4308
4505
  throw new Error("API key must be a string");
4309
4506
  }
4310
4507
  if (config.baseUrl != null && typeof config.baseUrl !== "string") {
@@ -4319,7 +4516,7 @@ function validateConfig(config) {
4319
4516
  }
4320
4517
  __name(validateConfig, "validateConfig");
4321
4518
  var devOverrideNotified = false;
4322
- function mergeConfig(userConfig) {
4519
+ function resolveConfig(userConfig) {
4323
4520
  validateConfig(userConfig);
4324
4521
  const merged = {
4325
4522
  ...DEFAULT_CONFIG,
@@ -4347,12 +4544,12 @@ function mergeConfig(userConfig) {
4347
4544
  };
4348
4545
  const dev = resolveDevOverride(userConfig.dev);
4349
4546
  if (dev.state === "active") {
4350
- merged.apiKey = dev.override.apiKey;
4351
- merged.dataset = dev.override.dataset;
4547
+ merged.apiKey = dev.apiKey;
4548
+ merged.dataset = dev.sandbox.datasetId;
4352
4549
  if (!devOverrideNotified) {
4353
4550
  devOverrideNotified = true;
4354
4551
  console.info(
4355
- `[RelyIO] Sandbox dev mode active (${dev.override.source}) \u2014 targeting dataset "${dev.override.dataset}" with a scoped preview key.`
4552
+ `[RelyIO] Sandbox dev mode active (${dev.sandbox.source}) \u2014 targeting dataset "${dev.sandbox.datasetId}" with a scoped preview key.`
4356
4553
  );
4357
4554
  }
4358
4555
  } else if (dev.state === "expired" && !devOverrideNotified) {
@@ -4366,9 +4563,9 @@ function mergeConfig(userConfig) {
4366
4563
  `[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.`
4367
4564
  );
4368
4565
  }
4369
- return merged;
4566
+ return { config: merged, sandbox: sandboxStateOf(dev) };
4370
4567
  }
4371
- __name(mergeConfig, "mergeConfig");
4568
+ __name(resolveConfig, "resolveConfig");
4372
4569
 
4373
4570
  // src/client/create-client.ts
4374
4571
  var API_VERSION_SEGMENT = "v1";
@@ -4376,17 +4573,20 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
4376
4573
  constructor(config) {
4377
4574
  __publicField(this, "httpClient");
4378
4575
  __publicField(this, "config");
4576
+ __publicField(this, "_sandbox");
4379
4577
  __publicField(this, "_search");
4380
4578
  __publicField(this, "_batch");
4381
4579
  __publicField(this, "_adhocQuery");
4382
4580
  __publicField(this, "_schema");
4383
4581
  const { schema, ...clientConfig } = config;
4384
4582
  this._schema = schema;
4385
- this.config = mergeConfig(clientConfig);
4583
+ const resolved = resolveConfig(clientConfig);
4584
+ this.config = resolved.config;
4585
+ this._sandbox = resolved.sandbox;
4386
4586
  this.httpClient = new HttpClient(this.config);
4387
- this._search = new SearchClient(this.httpClient);
4587
+ this._search = new SearchClient(this.httpClient, this.config.locale);
4388
4588
  this._batch = new BatchClient(this.httpClient);
4389
- this._adhocQuery = new AdhocQueryClient(this.httpClient);
4589
+ this._adhocQuery = new AdhocQueryClient(this.httpClient, this.config.locale);
4390
4590
  }
4391
4591
  // ============================================================================
4392
4592
  // CORE CLIENTS
@@ -4397,6 +4597,23 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
4397
4597
  get schema() {
4398
4598
  return this._schema;
4399
4599
  }
4600
+ /**
4601
+ * The `relyio dev` sandbox this client resolved at construction, as the preview toolbar displays
4602
+ * it (`sdk-surfaces-spec.md` decision 22).
4603
+ *
4604
+ * Plain, JSON-serializable data and no key, so a server component can hand it to a client
4605
+ * component as a prop — the browser cannot read the handshake itself, so a client created there
4606
+ * reports `absent`. See {@link SandboxState} for what each state means.
4607
+ *
4608
+ * @example
4609
+ * ```tsx
4610
+ * // app/layout.tsx — a server component handing the state to a client component
4611
+ * <SandboxBadge sandbox={relyio.sandbox} />
4612
+ * ```
4613
+ */
4614
+ get sandbox() {
4615
+ return this._sandbox;
4616
+ }
4400
4617
  /**
4401
4618
  * Get the ad-hoc query client
4402
4619
  */
@@ -4457,7 +4674,7 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
4457
4674
  * ```
4458
4675
  */
4459
4676
  singleton(key) {
4460
- return new SingletonClient(this.httpClient, key);
4677
+ return new SingletonClient(this.httpClient, key, this.config.locale);
4461
4678
  }
4462
4679
  /**
4463
4680
  * Get a settings client for a specific settings group
@@ -4471,13 +4688,16 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
4471
4688
  * ```
4472
4689
  */
4473
4690
  settings(groupId) {
4474
- return new SettingsClient(this.httpClient, groupId);
4691
+ return new SettingsClient(this.httpClient, groupId, this.config.locale);
4475
4692
  }
4476
4693
  // ============================================================================
4477
4694
  // SEARCH OPERATIONS
4478
4695
  // ============================================================================
4479
4696
  /**
4480
- * Start a search query with full-text, semantic, or hybrid search
4697
+ * Start a search query with full-text, semantic, or hybrid search.
4698
+ *
4699
+ * Where a schema types the client, the results are its entries — every content type's until
4700
+ * `in()` or `across()` narrows them (`INV-SS-TYPES-INFERRED`).
4481
4701
  *
4482
4702
  * @example
4483
4703
  * ```typescript
@@ -4575,6 +4795,25 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
4575
4795
  changes(options = {}) {
4576
4796
  return pollChanges(this.httpClient, options);
4577
4797
  }
4798
+ /**
4799
+ * The dataset's current position in the changes feed, as a cursor to start
4800
+ * `changes({ since })` or `followChanges(…, { since })` from. One
4801
+ * `GET /v1/changes` without a cursor: answered at once, holding nothing.
4802
+ *
4803
+ * Take it **before** you read what you will follow. A write your read did not
4804
+ * see is then after the position and replayed; opening the feed afterwards
4805
+ * without a `since` starts after it and misses it.
4806
+ *
4807
+ * @example
4808
+ * ```typescript
4809
+ * const since = await relyio.changesHead()
4810
+ * const post = await relyio.content('post').findById(id).execute()
4811
+ * for await (const change of relyio.changes({ since, ids: [id] })) { … }
4812
+ * ```
4813
+ */
4814
+ changesHead(options = {}) {
4815
+ return readChangesHead(this.httpClient, options);
4816
+ }
4578
4817
  // ============================================================================
4579
4818
  // QUERY OPERATIONS
4580
4819
  // ============================================================================
@@ -4612,14 +4851,33 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
4612
4851
  * ```
4613
4852
  */
4614
4853
  async executeQuery(queryName, params) {
4854
+ const clientLocale = this.config.locale;
4855
+ const namesLocale = params?.locale != null || params?.locales != null;
4615
4856
  const response = await this.httpClient.request({
4616
4857
  method: "GET",
4617
4858
  url: `/${API_VERSION_SEGMENT}/${String(queryName)}`,
4618
- params: params ?? {}
4859
+ params: namesLocale || clientLocale === void 0 || clientLocale === "" ? params ?? {} : { ...params, locale: clientLocale }
4619
4860
  });
4620
4861
  const payload = readPayload(response.body);
4621
4862
  return Array.isArray(payload) ? payload : [payload];
4622
4863
  }
4864
+ /**
4865
+ * Describe the project this key resolves to — `GET /v1/project`.
4866
+ *
4867
+ * `dataset` is the one this client's requests select (`dataset`, a sandbox from the `relyio dev`
4868
+ * handshake, or the key's default), which is not always the project's default, so a Studio link
4869
+ * built from `studioUrl` should carry it as `?dataset=`. `studioUrl` is `null` — a value, not an
4870
+ * error — when the service has no Dashboard address configured.
4871
+ *
4872
+ * @example
4873
+ * ```typescript
4874
+ * const { studioUrl, dataset } = await relyio.project()
4875
+ * if (studioUrl !== null) window.open(`${studioUrl}?dataset=${dataset.id}`)
4876
+ * ```
4877
+ */
4878
+ async project() {
4879
+ return this.httpClient.fetchPayload("/v1/project", "get", {});
4880
+ }
4623
4881
  /**
4624
4882
  * Get schema information from the API
4625
4883
  */
@@ -4666,11 +4924,13 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
4666
4924
  * Update the configuration and reinitialize all clients
4667
4925
  */
4668
4926
  updateConfig(newConfig) {
4669
- this.config = mergeConfig({ ...this.config, ...newConfig });
4927
+ const resolved = resolveConfig({ ...this.config, ...newConfig });
4928
+ this.config = resolved.config;
4929
+ this._sandbox = resolved.sandbox;
4670
4930
  this.httpClient = new HttpClient(this.config);
4671
- this._search = new SearchClient(this.httpClient);
4931
+ this._search = new SearchClient(this.httpClient, this.config.locale);
4672
4932
  this._batch = new BatchClient(this.httpClient);
4673
- this._adhocQuery = new AdhocQueryClient(this.httpClient);
4933
+ this._adhocQuery = new AdhocQueryClient(this.httpClient, this.config.locale);
4674
4934
  }
4675
4935
  /**
4676
4936
  * Get the current configuration
@@ -4696,7 +4956,10 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
4696
4956
  // ============================================================================
4697
4957
  /**
4698
4958
  * Create a locale-scoped client
4699
- * All content operations on the returned client will use the specified locale
4959
+ *
4960
+ * Every read the returned client builds — `content()`, `asset()`, `singleton()`, `settings()`,
4961
+ * `search()`, `similar()`, `autocomplete()`, `executeQuery()` and the ad-hoc queries — is in this
4962
+ * locale unless the call names one of its own, which wins.
4700
4963
  *
4701
4964
  * Type-safe when schema is provided: autocompletes valid locale codes!
4702
4965
  *
@@ -4704,9 +4967,10 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
4704
4967
  * ```typescript
4705
4968
  * const spanish = relyio.withLocale('es-ES') // ← Autocompletes with schema!
4706
4969
  *
4707
- * // All operations use Spanish
4970
+ * // Reads use Spanish
4708
4971
  * const products = await spanish.content('product').query().execute()
4709
4972
  * const homepage = await spanish.singleton('homepage').fetch()
4973
+ * const results = await spanish.search('zapatos').execute()
4710
4974
  *
4711
4975
  * // Original client unchanged
4712
4976
  * const englishProducts = await relyio.content('product').query().execute()
@@ -4763,6 +5027,6 @@ function createClient(config) {
4763
5027
  }
4764
5028
  __name(createClient, "createClient");
4765
5029
 
4766
- export { AdhocQueryBuilder, AdhocQueryClient, AssetClient, AssetFileBuilder, AssetUploadBuilder, AuthenticationError, AuthorizationError, AutocompleteBuilder, BaseValidationError, BatchBuilder, BatchClient, ChangesExpiredError, ConnectionError, ContentByIdBuilder, ContentClient, ContentQueryBuilder, ErrorMapper, FileUploadError, FileValidationError, HttpClient, InvalidApiKeyError, InvalidFieldTypeError, InvalidPayloadError, MethodNotAllowedError, MissingApiKeyError, NetworkError, PayloadTooLargeError, PlanFeatureUnavailableError, RateLimitError, ReadOnlyKeyError, RequiredFieldError, ResourceConflictError, ResourceExpiredError, ResourceNotFoundError, SDKError, SchemaValidationError, SearchBuilder, SearchClient, ServerError, ServiceIndeterminateError, SettingsClient, SimilarBuilder, SingletonClient, TimeoutError, TransactionRolledBackError, TypedRelyIOClient, UnknownContractError, UsageQuotaExceededError, ValidationError, createClient, entityTagOf, isAssetEntry, isResourceNotFound, matchRoute, validateFile };
4767
- //# sourceMappingURL=chunk-43VPZT2G.js.map
4768
- //# sourceMappingURL=chunk-43VPZT2G.js.map
5030
+ export { AdhocQueryBuilder, AdhocQueryClient, AssetClient, AssetFileBuilder, AssetUploadBuilder, AuthenticationError, AuthorizationError, AutocompleteBuilder, BaseValidationError, BatchBuilder, BatchClient, ChangesExpiredError, ConnectionError, ContentByIdBuilder, ContentClient, ContentQueryBuilder, ErrorMapper, FileUploadError, FileValidationError, HttpClient, InvalidApiKeyError, InvalidFieldTypeError, InvalidPayloadError, MethodNotAllowedError, MissingApiKeyError, NetworkError, PayloadTooLargeError, PlanFeatureUnavailableError, RateLimitError, ReadOnlyKeyError, RequiredFieldError, ResourceConflictError, ResourceExpiredError, ResourceNotFoundError, SDKError, SchemaValidationError, SearchBuilder, SearchClient, ServerError, ServiceIndeterminateError, SettingsClient, SimilarBuilder, SingletonClient, TimeoutError, TransactionRolledBackError, TypedRelyIOClient, UnknownContractError, UsageQuotaExceededError, ValidationError, createClient, entityTagOf, isAborted, isAssetEntry, isResourceNotFound, matchRoute, serviceInstructedDelayMs, sleep, validateFile };
5031
+ //# sourceMappingURL=chunk-7ABNXVWL.js.map
5032
+ //# sourceMappingURL=chunk-7ABNXVWL.js.map