@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
@@ -71,44 +71,6 @@ var _SDKError = class _SDKError extends Error {
71
71
  chunkMWMSFIWN_cjs.__name(_SDKError, "SDKError");
72
72
  var SDKError = _SDKError;
73
73
 
74
- // src/errors/auth-errors.ts
75
- var _AuthenticationError = class _AuthenticationError extends SDKError {
76
- constructor(message = "Authentication failed", statusCode = 401, cause, context) {
77
- super(message, statusCode, cause, context);
78
- chunkMWMSFIWN_cjs.__publicField(this, "code", "AUTHENTICATION_ERROR");
79
- chunkMWMSFIWN_cjs.__publicField(this, "retryable", false);
80
- }
81
- getUserMessage() {
82
- return "Authentication failed. Please check your API key and try again.";
83
- }
84
- };
85
- chunkMWMSFIWN_cjs.__name(_AuthenticationError, "AuthenticationError");
86
- var AuthenticationError = _AuthenticationError;
87
- var _AuthorizationError = class _AuthorizationError extends SDKError {
88
- constructor(message = "Authorization failed", statusCode = 403, cause, context) {
89
- super(message, statusCode, cause, context);
90
- chunkMWMSFIWN_cjs.__publicField(this, "code", "AUTHORIZATION_ERROR");
91
- chunkMWMSFIWN_cjs.__publicField(this, "retryable", false);
92
- }
93
- getUserMessage() {
94
- return "You do not have permission to access this resource.";
95
- }
96
- };
97
- chunkMWMSFIWN_cjs.__name(_AuthorizationError, "AuthorizationError");
98
- var AuthorizationError = _AuthorizationError;
99
- var _InvalidApiKeyError = class _InvalidApiKeyError extends SDKError {
100
- constructor(message = "Invalid API key", cause, context) {
101
- super(message, 401, cause, context);
102
- chunkMWMSFIWN_cjs.__publicField(this, "code", "INVALID_API_KEY");
103
- chunkMWMSFIWN_cjs.__publicField(this, "retryable", false);
104
- }
105
- getUserMessage() {
106
- return "Invalid API key. Please check your API key and try again.";
107
- }
108
- };
109
- chunkMWMSFIWN_cjs.__name(_InvalidApiKeyError, "InvalidApiKeyError");
110
- var InvalidApiKeyError = _InvalidApiKeyError;
111
-
112
74
  // src/errors/contract-errors.ts
113
75
  var _MissingApiKeyError = class _MissingApiKeyError extends SDKError {
114
76
  constructor(message = "No API key was sent", context) {
@@ -269,6 +231,44 @@ var _UnknownContractError = class _UnknownContractError extends SDKError {
269
231
  chunkMWMSFIWN_cjs.__name(_UnknownContractError, "UnknownContractError");
270
232
  var UnknownContractError = _UnknownContractError;
271
233
 
234
+ // 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
+ var _AuthorizationError = class _AuthorizationError extends SDKError {
248
+ constructor(message = "Authorization failed", statusCode = 403, cause, context) {
249
+ super(message, statusCode, cause, context);
250
+ chunkMWMSFIWN_cjs.__publicField(this, "code", "AUTHORIZATION_ERROR");
251
+ chunkMWMSFIWN_cjs.__publicField(this, "retryable", false);
252
+ }
253
+ getUserMessage() {
254
+ return "You do not have permission to access this resource.";
255
+ }
256
+ };
257
+ chunkMWMSFIWN_cjs.__name(_AuthorizationError, "AuthorizationError");
258
+ var AuthorizationError = _AuthorizationError;
259
+ var _InvalidApiKeyError = class _InvalidApiKeyError extends SDKError {
260
+ constructor(message = "Invalid API key", cause, context) {
261
+ super(message, 401, cause, context);
262
+ chunkMWMSFIWN_cjs.__publicField(this, "code", "INVALID_API_KEY");
263
+ chunkMWMSFIWN_cjs.__publicField(this, "retryable", false);
264
+ }
265
+ getUserMessage() {
266
+ return "Invalid API key. Please check your API key and try again.";
267
+ }
268
+ };
269
+ chunkMWMSFIWN_cjs.__name(_InvalidApiKeyError, "InvalidApiKeyError");
270
+ var InvalidApiKeyError = _InvalidApiKeyError;
271
+
272
272
  // src/errors/content-errors.ts
273
273
  var _ResourceNotFoundError = class _ResourceNotFoundError extends SDKError {
274
274
  constructor(message = "Resource not found", statusCode = 404, cause, context) {
@@ -330,8 +330,12 @@ var _NetworkError = class _NetworkError extends SDKError {
330
330
  chunkMWMSFIWN_cjs.__name(_NetworkError, "NetworkError");
331
331
  var NetworkError = _NetworkError;
332
332
  var _TimeoutError = class _TimeoutError extends SDKError {
333
- constructor(message = "Request timed out", timeout, statusCode, cause) {
334
- super(message, statusCode, cause, { timeout });
333
+ /**
334
+ * @param context - Carried beside `timeout`. The mapper passes a `REQUEST_TIMEOUT` refusal's body
335
+ * as `context.response`, as it does for every other contract code.
336
+ */
337
+ constructor(message = "Request timed out", timeout, statusCode, cause, context) {
338
+ super(message, statusCode, cause, { ...context, timeout });
335
339
  chunkMWMSFIWN_cjs.__publicField(this, "code", "TIMEOUT_ERROR");
336
340
  chunkMWMSFIWN_cjs.__publicField(this, "retryable", true);
337
341
  }
@@ -357,8 +361,13 @@ var ConnectionError = _ConnectionError;
357
361
 
358
362
  // src/errors/validation-errors.ts
359
363
  var _BaseValidationError = class _BaseValidationError extends SDKError {
360
- constructor(message = "Validation failed", violations = [], statusCode = 400, cause) {
361
- super(message, statusCode, cause, { violations });
364
+ /**
365
+ * @param context - Carried beside `violations`. The mapper passes the refusal body as
366
+ * `context.response`, as it does for every other contract code, so a consumer that hands the
367
+ * service's own words on can read them here too.
368
+ */
369
+ constructor(message = "Validation failed", violations = [], statusCode = 400, cause, context) {
370
+ super(message, statusCode, cause, { ...context, violations });
362
371
  chunkMWMSFIWN_cjs.__publicField(this, "retryable", false);
363
372
  chunkMWMSFIWN_cjs.__publicField(this, "violations");
364
373
  this.violations = violations;
@@ -403,8 +412,8 @@ var _ValidationError = class _ValidationError extends BaseValidationError {
403
412
  chunkMWMSFIWN_cjs.__name(_ValidationError, "ValidationError");
404
413
  var ValidationError = _ValidationError;
405
414
  var _InvalidPayloadError = class _InvalidPayloadError extends BaseValidationError {
406
- constructor(message = "Invalid payload", violations = [], cause) {
407
- super(message, violations, 400, cause);
415
+ constructor(message = "Invalid payload", violations = [], cause, context) {
416
+ super(message, violations, 400, cause, context);
408
417
  chunkMWMSFIWN_cjs.__publicField(this, "code", "INVALID_PAYLOAD");
409
418
  }
410
419
  getUserMessage() {
@@ -593,10 +602,10 @@ var _ErrorMapper = class _ErrorMapper {
593
602
  );
594
603
  }
595
604
  case "REQUEST_INVALID": {
596
- return new InvalidPayloadError(message, violationsOf(refusal));
605
+ return new InvalidPayloadError(message, violationsOf(refusal), void 0, context);
597
606
  }
598
607
  case "CONTENT_INVALID": {
599
- return new ValidationError(message, violationsOf(refusal), 400);
608
+ return new ValidationError(message, violationsOf(refusal), 400, void 0, context);
600
609
  }
601
610
  case "METHOD_NOT_ALLOWED": {
602
611
  return new MethodNotAllowedError(message, context);
@@ -619,7 +628,7 @@ var _ErrorMapper = class _ErrorMapper {
619
628
  return new ResourceExpiredError(message, 410, void 0, context);
620
629
  }
621
630
  case "REQUEST_TIMEOUT": {
622
- return new TimeoutError(message, void 0, void 0, void 0);
631
+ return new TimeoutError(message, void 0, status, void 0, context);
623
632
  }
624
633
  case "PAYLOAD_TOO_LARGE": {
625
634
  return new PayloadTooLargeError(message, detailNumber(refusal, "limitBytes"), context);
@@ -770,6 +779,7 @@ var DECLARED_PATHS = {
770
779
  "/v1/meta/schema": true,
771
780
  "/v1/meta/schema/{contentType}": true,
772
781
  "/v1/meta/schema/{contentType}/fields": true,
782
+ "/v1/project": true,
773
783
  "/v1/query": true,
774
784
  "/v1/search": true,
775
785
  "/v1/search/autocomplete": true,
@@ -841,20 +851,29 @@ function readQuery(searchParams) {
841
851
  }
842
852
  chunkMWMSFIWN_cjs.__name(readQuery, "readQuery");
843
853
 
844
- // src/http/retry-policy.ts
845
- var IDEMPOTENT_METHODS = /* @__PURE__ */ new Set([
846
- "GET",
847
- "HEAD",
848
- "OPTIONS",
849
- "PUT",
850
- "DELETE",
851
- "TRACE"
852
- ]);
853
- function isReplaySafe(request) {
854
- if (IDEMPOTENT_METHODS.has(request.method.toUpperCase())) return true;
855
- return headerValue(request.headers, HEADER_IDEMPOTENCY_KEY.name) !== void 0;
854
+ // src/http/abortable-sleep.ts
855
+ function sleep(ms, signal) {
856
+ return new Promise((resolve) => {
857
+ if (isAborted(signal)) {
858
+ resolve();
859
+ return;
860
+ }
861
+ const done = /* @__PURE__ */ chunkMWMSFIWN_cjs.__name(() => {
862
+ clearTimeout(timer);
863
+ signal?.removeEventListener("abort", done);
864
+ resolve();
865
+ }, "done");
866
+ const timer = setTimeout(done, ms);
867
+ signal?.addEventListener("abort", done, { once: true });
868
+ });
856
869
  }
857
- chunkMWMSFIWN_cjs.__name(isReplaySafe, "isReplaySafe");
870
+ chunkMWMSFIWN_cjs.__name(sleep, "sleep");
871
+ function isAborted(signal) {
872
+ return signal?.aborted === true;
873
+ }
874
+ chunkMWMSFIWN_cjs.__name(isAborted, "isAborted");
875
+
876
+ // src/errors/instructed-delay.ts
858
877
  function serviceInstructedDelayMs(error, nowMs) {
859
878
  if (error instanceof ServiceIndeterminateError) {
860
879
  return error.retryAfterMs;
@@ -868,6 +887,21 @@ function serviceInstructedDelayMs(error, nowMs) {
868
887
  return wait > 0 ? wait : void 0;
869
888
  }
870
889
  chunkMWMSFIWN_cjs.__name(serviceInstructedDelayMs, "serviceInstructedDelayMs");
890
+
891
+ // src/http/retry-policy.ts
892
+ var IDEMPOTENT_METHODS = /* @__PURE__ */ new Set([
893
+ "GET",
894
+ "HEAD",
895
+ "OPTIONS",
896
+ "PUT",
897
+ "DELETE",
898
+ "TRACE"
899
+ ]);
900
+ function isReplaySafe(request) {
901
+ if (IDEMPOTENT_METHODS.has(request.method.toUpperCase())) return true;
902
+ return headerValue(request.headers, HEADER_IDEMPOTENCY_KEY.name) !== void 0;
903
+ }
904
+ chunkMWMSFIWN_cjs.__name(isReplaySafe, "isReplaySafe");
871
905
  var _DefaultRetryPolicy = class _DefaultRetryPolicy {
872
906
  constructor(config) {
873
907
  chunkMWMSFIWN_cjs.__publicField(this, "config");
@@ -904,9 +938,10 @@ var _HttpClient = class _HttpClient {
904
938
  this.timeout = config.timeout;
905
939
  this.debug = config.debug.enabled ?? false;
906
940
  this.logger = config.debug.logger;
941
+ const apiKey = presentKey(config.apiKey);
907
942
  this.defaultHeaders = {
908
943
  "Content-Type": "application/json",
909
- "X-API-Key": config.apiKey,
944
+ ...apiKey === void 0 ? {} : { [API_KEY_HEADER]: apiKey },
910
945
  ...config.headers
911
946
  };
912
947
  if (config.dataset != null) {
@@ -965,9 +1000,21 @@ var _HttpClient = class _HttpClient {
965
1000
  * The loop always makes one attempt. The incumbent's `while (attempt <
966
1001
  * maxAttempts)` made none at `retryAttempts: 0` — a value the config validator
967
1002
  * accepts — and then dereferenced an unassigned `lastError`.
1003
+ *
1004
+ * A request that would carry no `X-API-Key` makes none: it rejects with
1005
+ * `MissingApiKeyError` before `fetch` is called, is not retried, and names
1006
+ * `apiKey`. This is where a client built without a key learns it — the
1007
+ * constructor accepts one so that a module-scope client can be evaluated by a
1008
+ * build that holds no key (REL-1120).
968
1009
  */
969
1010
  async request(config) {
970
1011
  const mergedConfig = this.mergeRequestConfig(config);
1012
+ if (presentKey(headerValue(mergedConfig.headers, API_KEY_HEADER)) === void 0) {
1013
+ 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.",
1015
+ { url: mergedConfig.url, method: mergedConfig.method, sent: false }
1016
+ );
1017
+ }
971
1018
  this.log("debug", "Making request", {
972
1019
  method: mergedConfig.method,
973
1020
  url: mergedConfig.url
@@ -996,7 +1043,7 @@ var _HttpClient = class _HttpClient {
996
1043
  if (this.retryPolicy.shouldRetry(attempt, failure, mergedConfig)) {
997
1044
  const delay = this.retryPolicy.delayFor(attempt, failure);
998
1045
  this.log("debug", "Retrying request", { attempt, delay });
999
- await this.sleep(delay, config.signal);
1046
+ await sleep(delay, config.signal);
1000
1047
  continue;
1001
1048
  }
1002
1049
  const sdkError = ErrorMapper.mapGenericError(failure, {
@@ -1125,8 +1172,12 @@ var _HttpClient = class _HttpClient {
1125
1172
  headers: filteredHeaders,
1126
1173
  body: this.prepareRequestBody(config),
1127
1174
  signal: controller.signal,
1128
- // Include credentials for CORS requests if in browser and configured
1129
- ...typeof window !== "undefined" && this.config.browser.includeCredentials != null && { credentials: "include" }
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)
1180
+ }
1130
1181
  };
1131
1182
  const send = this.config.fetch ?? globalThis.fetch;
1132
1183
  const response = await send(url, requestInit);
@@ -1282,22 +1333,6 @@ var _HttpClient = class _HttpClient {
1282
1333
  });
1283
1334
  return obj;
1284
1335
  }
1285
- /** A backoff the caller's abort cuts short; the next attempt then refuses to send. */
1286
- sleep(ms, signal) {
1287
- return new Promise((resolve) => {
1288
- if (isAborted(signal)) {
1289
- resolve();
1290
- return;
1291
- }
1292
- const done = /* @__PURE__ */ chunkMWMSFIWN_cjs.__name(() => {
1293
- clearTimeout(timer);
1294
- signal?.removeEventListener("abort", done);
1295
- resolve();
1296
- }, "done");
1297
- const timer = setTimeout(done, ms);
1298
- signal?.addEventListener("abort", done, { once: true });
1299
- });
1300
- }
1301
1336
  log(level, message, data) {
1302
1337
  if (!this.debug) return;
1303
1338
  if (this.logger != null) {
@@ -1309,10 +1344,23 @@ var _HttpClient = class _HttpClient {
1309
1344
  };
1310
1345
  chunkMWMSFIWN_cjs.__name(_HttpClient, "HttpClient");
1311
1346
  var HttpClient = _HttpClient;
1312
- function isAborted(signal) {
1313
- return signal?.aborted === true;
1347
+ var API_KEY_HEADER = "X-API-Key";
1348
+ function presentKey(value) {
1349
+ return typeof value === "string" && value.trim() !== "" ? value : void 0;
1314
1350
  }
1315
- chunkMWMSFIWN_cjs.__name(isAborted, "isAborted");
1351
+ chunkMWMSFIWN_cjs.__name(presentKey, "presentKey");
1352
+ 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
+ }
1362
+ }
1363
+ chunkMWMSFIWN_cjs.__name(credentialsModeFor, "credentialsModeFor");
1316
1364
 
1317
1365
  // src/content/query-builder.ts
1318
1366
  var _ContentQueryBuilder = class _ContentQueryBuilder {
@@ -1328,6 +1376,16 @@ var _ContentQueryBuilder = class _ContentQueryBuilder {
1328
1376
  chunkMWMSFIWN_cjs.__publicField(this, "childrenOptions");
1329
1377
  chunkMWMSFIWN_cjs.__publicField(this, "pageNumber");
1330
1378
  chunkMWMSFIWN_cjs.__publicField(this, "pageSizeValue");
1379
+ chunkMWMSFIWN_cjs.__publicField(this, "requestHeaders");
1380
+ }
1381
+ /**
1382
+ * Headers this query adds to the client's own, e.g. `X-RelyIO-Dataset` to list another dataset
1383
+ * the key can see. A name the client already sends is replaced for this query only; the client
1384
+ * is not changed.
1385
+ */
1386
+ headers(headers) {
1387
+ this.requestHeaders = headers;
1388
+ return this;
1331
1389
  }
1332
1390
  /**
1333
1391
  * Add where conditions to the query
@@ -1525,7 +1583,11 @@ var _ContentQueryBuilder = class _ContentQueryBuilder {
1525
1583
  const response = await this.httpClient.send(
1526
1584
  "/v1/{contentType}",
1527
1585
  "get",
1528
- { path: { contentType: this.contentType }, query: this.buildQueryParams() }
1586
+ {
1587
+ path: { contentType: this.contentType },
1588
+ query: this.buildQueryParams(),
1589
+ headers: this.requestHeaders
1590
+ }
1529
1591
  );
1530
1592
  return response.body;
1531
1593
  }
@@ -1593,6 +1655,7 @@ var _ContentQueryBuilder = class _ContentQueryBuilder {
1593
1655
  cloned.fieldsProjection = this.fieldsProjection ? [...this.fieldsProjection] : void 0;
1594
1656
  cloned.pageNumber = this.pageNumber;
1595
1657
  cloned.pageSizeValue = this.pageSizeValue;
1658
+ cloned.requestHeaders = this.requestHeaders ? { ...this.requestHeaders } : void 0;
1596
1659
  return cloned;
1597
1660
  }
1598
1661
  /**
@@ -1700,6 +1763,7 @@ var _ContentByIdBuilder = class _ContentByIdBuilder {
1700
1763
  chunkMWMSFIWN_cjs.__publicField(this, "localesArray");
1701
1764
  chunkMWMSFIWN_cjs.__publicField(this, "fetchAllLocales", false);
1702
1765
  chunkMWMSFIWN_cjs.__publicField(this, "childrenOptions");
1766
+ chunkMWMSFIWN_cjs.__publicField(this, "requestHeaders");
1703
1767
  }
1704
1768
  /**
1705
1769
  * Include related content in the response
@@ -1730,6 +1794,15 @@ var _ContentByIdBuilder = class _ContentByIdBuilder {
1730
1794
  this.fetchAllLocales = false;
1731
1795
  return this;
1732
1796
  }
1797
+ /**
1798
+ * Headers this one read adds to the client's own, e.g. `X-RelyIO-Dataset` to read another dataset
1799
+ * the key can see. A name the client already sends is replaced for this request only; the client
1800
+ * is not changed.
1801
+ */
1802
+ headers(headers) {
1803
+ this.requestHeaders = headers;
1804
+ return this;
1805
+ }
1733
1806
  /**
1734
1807
  * Fetch content in all available locales
1735
1808
  * Returns default locale at top level, others in _metadata.localizations
@@ -1783,7 +1856,11 @@ var _ContentByIdBuilder = class _ContentByIdBuilder {
1783
1856
  return await this.httpClient.fetchPayload(
1784
1857
  "/v1/{contentType}/{id}",
1785
1858
  "get",
1786
- { path: { contentType: this.contentType, id: this.id }, query: params }
1859
+ {
1860
+ path: { contentType: this.contentType, id: this.id },
1861
+ query: params,
1862
+ headers: this.requestHeaders
1863
+ }
1787
1864
  );
1788
1865
  } catch (error) {
1789
1866
  if (isResourceNotFound(error)) return null;
@@ -1813,6 +1890,14 @@ function writeHeaders(options) {
1813
1890
  return Object.keys(headers).length > 0 ? headers : void 0;
1814
1891
  }
1815
1892
  chunkMWMSFIWN_cjs.__name(writeHeaders, "writeHeaders");
1893
+ function requestHeaders(headers, typed) {
1894
+ if (headers === void 0) return typed;
1895
+ if (typed === void 0) return headers;
1896
+ const typedNames = new Set(Object.keys(typed).map((name) => name.toLowerCase()));
1897
+ const own = Object.entries(headers).filter(([name]) => !typedNames.has(name.toLowerCase()));
1898
+ return { ...Object.fromEntries(own), ...typed };
1899
+ }
1900
+ chunkMWMSFIWN_cjs.__name(requestHeaders, "requestHeaders");
1816
1901
  function entityTagOf(response) {
1817
1902
  return headerValue(response.headers, HEADER_ENTITY_TAG.name);
1818
1903
  }
@@ -1882,7 +1967,8 @@ var _ContentClient = class _ContentClient {
1882
1967
  /**
1883
1968
  * Fetch a single content item by ID
1884
1969
  * @param id - Entry ID or slug
1885
- * @param options - Fetch options (include, locale)
1970
+ * @param options - Fetch options (include, locale, and `headers` this one request adds — e.g.
1971
+ * `{ 'X-RelyIO-Dataset': 'staging' }` to read a dataset other than the client's)
1886
1972
  */
1887
1973
  async fetchOne(id, options) {
1888
1974
  const builder = this.findById(id);
@@ -1892,6 +1978,9 @@ var _ContentClient = class _ContentClient {
1892
1978
  if (options?.locale != null && options.locale !== "") {
1893
1979
  builder.locale(options.locale);
1894
1980
  }
1981
+ if (options?.headers != null) {
1982
+ builder.headers(options.headers);
1983
+ }
1895
1984
  return builder.execute();
1896
1985
  }
1897
1986
  /**
@@ -1906,6 +1995,11 @@ var _ContentClient = class _ContentClient {
1906
1995
  * `entityTag` is `undefined` when the response came from the cache: a cached
1907
1996
  * body cannot know the current version, and an absent tag beats a stale one
1908
1997
  * that would refuse the next write.
1998
+ *
1999
+ * Unlike {@link fetchOne}, a missing entry is not `null`: the service's
2000
+ * `RESOURCE_NOT_FOUND` is thrown as it arrived, with the `kind` that says
2001
+ * whether the entry, the content type or the schema was missing. `headers`
2002
+ * are the ones this read adds, as for `fetchOne`.
1909
2003
  */
1910
2004
  async fetchOneWithTag(id, options) {
1911
2005
  const query = {};
@@ -1915,7 +2009,7 @@ var _ContentClient = class _ContentClient {
1915
2009
  const response = await this.httpClient.send(
1916
2010
  "/v1/{contentType}/{id}",
1917
2011
  "get",
1918
- { path: { contentType: this.contentType, id }, query }
2012
+ { path: { contentType: this.contentType, id }, query, headers: options?.headers }
1919
2013
  );
1920
2014
  return { entry: readPayload(response.body), entityTag: entityTagOf(response) };
1921
2015
  }
@@ -1937,17 +2031,20 @@ var _ContentClient = class _ContentClient {
1937
2031
  /**
1938
2032
  * Create a new content item
1939
2033
  * @param data - Content data
1940
- * @param options - Create options (slug, locale, parentId, sortOrder)
2034
+ * @param options - Create options (slug, locale, parentId, sortOrder), and `headers` this one
2035
+ * request adds — e.g. `{ 'X-RelyIO-Dataset': 'staging' }` to write a dataset other than the
2036
+ * client's. They are sent, never put in the body; `idempotencyKey` still wins over an
2037
+ * `Idempotency-Key` among them.
1941
2038
  */
1942
2039
  async create(data, options) {
1943
- const { idempotencyKey, ...body } = options ?? {};
2040
+ const { idempotencyKey, headers, ...body } = options ?? {};
1944
2041
  return this.httpClient.fetchPayload(
1945
2042
  "/v1/{contentType}",
1946
2043
  "post",
1947
2044
  {
1948
2045
  path: { contentType: this.contentType },
1949
2046
  body: { content: data, ...body },
1950
- headers: writeHeaders({ idempotencyKey })
2047
+ headers: requestHeaders(headers, writeHeaders({ idempotencyKey }))
1951
2048
  }
1952
2049
  );
1953
2050
  }
@@ -1955,17 +2052,19 @@ var _ContentClient = class _ContentClient {
1955
2052
  * Update a content item by ID (partial update)
1956
2053
  * @param id - Entry ID
1957
2054
  * @param data - Fields to update
1958
- * @param options - Update options (slug, locale, parentId, sortOrder)
2055
+ * @param options - Update options (slug, locale, parentId, sortOrder), and `headers` this one
2056
+ * request adds, as for {@link create}. `ifMatch` and `idempotencyKey` win over the same
2057
+ * header among them.
1959
2058
  */
1960
2059
  async update(id, data, options) {
1961
- const { idempotencyKey, ifMatch, ...body } = options ?? {};
2060
+ const { idempotencyKey, ifMatch, headers, ...body } = options ?? {};
1962
2061
  return this.httpClient.fetchPayload(
1963
2062
  "/v1/{contentType}/{id}",
1964
2063
  "patch",
1965
2064
  {
1966
2065
  path: { contentType: this.contentType, id },
1967
2066
  body: { content: data, ...body },
1968
- headers: writeHeaders({ idempotencyKey, ifMatch })
2067
+ headers: requestHeaders(headers, writeHeaders({ idempotencyKey, ifMatch }))
1969
2068
  }
1970
2069
  );
1971
2070
  }
@@ -2060,8 +2159,9 @@ var _ContentClient = class _ContentClient {
2060
2159
  if (options?.include != null) {
2061
2160
  params.include = options.include.join(",");
2062
2161
  }
2063
- if (options?.locale != null && options.locale !== "") {
2064
- params.locale = options.locale;
2162
+ const locale = options?.locale ?? this.defaultLocale;
2163
+ if (locale != null && locale !== "") {
2164
+ params.locale = locale;
2065
2165
  }
2066
2166
  return this.httpClient.fetchPayload(
2067
2167
  "/v1/{contentType}/{id}/children",
@@ -2719,9 +2819,10 @@ chunkMWMSFIWN_cjs.__name(isAssetEntry, "isAssetEntry");
2719
2819
 
2720
2820
  // src/content/singleton-client.ts
2721
2821
  var _SingletonClient = class _SingletonClient {
2722
- constructor(httpClient, singletonKey) {
2822
+ constructor(httpClient, singletonKey, defaultLocale) {
2723
2823
  this.httpClient = httpClient;
2724
2824
  this.singletonKey = singletonKey;
2825
+ this.defaultLocale = defaultLocale;
2725
2826
  chunkMWMSFIWN_cjs.__publicField(this, "includeFields");
2726
2827
  chunkMWMSFIWN_cjs.__publicField(this, "localeValue");
2727
2828
  }
@@ -2746,11 +2847,6 @@ var _SingletonClient = class _SingletonClient {
2746
2847
  * @param options - Optional fetch options (alternative to fluent methods)
2747
2848
  */
2748
2849
  async fetch(options) {
2749
- const includeValue = options?.include ?? this.includeFields;
2750
- options?.locale ?? this.localeValue;
2751
- if (includeValue) {
2752
- includeValue.join(",");
2753
- }
2754
2850
  try {
2755
2851
  return (await this.fetchWithTag(options)).content;
2756
2852
  } catch (error) {
@@ -2768,7 +2864,7 @@ var _SingletonClient = class _SingletonClient {
2768
2864
  async fetchWithTag(options) {
2769
2865
  const query = {};
2770
2866
  const includeValue = options?.include ?? this.includeFields;
2771
- const localeValueFinal = options?.locale ?? this.localeValue;
2867
+ const localeValueFinal = options?.locale ?? this.localeValue ?? this.defaultLocale;
2772
2868
  if (includeValue) query.include = includeValue.join(",");
2773
2869
  if (localeValueFinal != null && localeValueFinal !== "") query.locale = localeValueFinal;
2774
2870
  const response = await this.httpClient.send(
@@ -2865,9 +2961,10 @@ var SingletonClient = _SingletonClient;
2865
2961
 
2866
2962
  // src/content/settings-client.ts
2867
2963
  var _SettingsClient = class _SettingsClient {
2868
- constructor(httpClient, groupId) {
2964
+ constructor(httpClient, groupId, defaultLocale) {
2869
2965
  this.httpClient = httpClient;
2870
2966
  this.groupId = groupId;
2967
+ this.defaultLocale = defaultLocale;
2871
2968
  chunkMWMSFIWN_cjs.__publicField(this, "includeFields");
2872
2969
  chunkMWMSFIWN_cjs.__publicField(this, "localeValue");
2873
2970
  }
@@ -2892,11 +2989,6 @@ var _SettingsClient = class _SettingsClient {
2892
2989
  * @param options - Optional fetch options (alternative to fluent methods)
2893
2990
  */
2894
2991
  async fetch(options) {
2895
- const includeValue = options?.include ?? this.includeFields;
2896
- options?.locale ?? this.localeValue;
2897
- if (includeValue) {
2898
- includeValue.join(",");
2899
- }
2900
2992
  try {
2901
2993
  return (await this.fetchWithTag(options)).settings;
2902
2994
  } catch (error) {
@@ -2926,7 +3018,7 @@ var _SettingsClient = class _SettingsClient {
2926
3018
  async fetchWithTag(options) {
2927
3019
  const query = {};
2928
3020
  const includeValue = options?.include ?? this.includeFields;
2929
- const localeValueFinal = options?.locale ?? this.localeValue;
3021
+ const localeValueFinal = options?.locale ?? this.localeValue ?? this.defaultLocale;
2930
3022
  if (includeValue) query.include = includeValue.join(",");
2931
3023
  if (localeValueFinal != null && localeValueFinal !== "") query.locale = localeValueFinal;
2932
3024
  const response = await this.httpClient.send(
@@ -3028,14 +3120,28 @@ var SettingsClient = _SettingsClient;
3028
3120
 
3029
3121
  // src/search/search-client.ts
3030
3122
  var _SearchBuilder = class _SearchBuilder {
3031
- constructor(httpClient, query) {
3123
+ /**
3124
+ * @param defaultLocale - The client's locale (`withLocale()`), sent when `.locale()` names none
3125
+ */
3126
+ constructor(httpClient, query, defaultLocale) {
3032
3127
  this.httpClient = httpClient;
3128
+ this.defaultLocale = defaultLocale;
3033
3129
  chunkMWMSFIWN_cjs.__publicField(this, "query");
3034
3130
  chunkMWMSFIWN_cjs.__publicField(this, "searchOptions", {});
3131
+ chunkMWMSFIWN_cjs.__publicField(this, "requestHeaders");
3035
3132
  this.query = query;
3036
3133
  }
3037
3134
  /**
3038
- * Search within a single content type
3135
+ * Headers this search adds to the client's own, e.g. `X-RelyIO-Dataset` to search another
3136
+ * dataset the key can see. A name the client already sends is replaced for this search only.
3137
+ */
3138
+ headers(headers) {
3139
+ this.requestHeaders = headers;
3140
+ return this;
3141
+ }
3142
+ /**
3143
+ * Search within a single content type. Where a schema types the client, the name is one of its
3144
+ * content types and the results are that type's entries.
3039
3145
  * @param contentType - Content type to search in
3040
3146
  */
3041
3147
  in(contentType) {
@@ -3044,7 +3150,8 @@ var _SearchBuilder = class _SearchBuilder {
3044
3150
  return this;
3045
3151
  }
3046
3152
  /**
3047
- * Search across multiple content types
3153
+ * Search across multiple content types. Where a schema types the client, the names are its content
3154
+ * types and the results are a union of their entries — every type's, for `'*'`.
3048
3155
  * @param contentTypes - Array of content types or '*' for all
3049
3156
  */
3050
3157
  across(contentTypes) {
@@ -3079,6 +3186,16 @@ var _SearchBuilder = class _SearchBuilder {
3079
3186
  this.searchOptions.fields = projection;
3080
3187
  return this;
3081
3188
  }
3189
+ /**
3190
+ * Order the results by a field instead of by relevance. Called again, it adds a tie-breaker.
3191
+ * `_score` names relevance itself.
3192
+ * @param field - Field path to sort by (e.g. `updatedAt`, `pricing.basePrice`)
3193
+ * @param direction - `asc` or `desc` (the route's default is `desc`)
3194
+ */
3195
+ sort(field, direction = "desc") {
3196
+ this.searchOptions.sort = [...this.searchOptions.sort ?? [], { field, direction }];
3197
+ return this;
3198
+ }
3082
3199
  /**
3083
3200
  * Limit number of results
3084
3201
  * @param count - Maximum results
@@ -3139,6 +3256,9 @@ var _SearchBuilder = class _SearchBuilder {
3139
3256
  if (this.searchOptions.fields) {
3140
3257
  body.fields = this.searchOptions.fields;
3141
3258
  }
3259
+ if (this.searchOptions.sort) {
3260
+ body.sort = this.searchOptions.sort;
3261
+ }
3142
3262
  if (this.searchOptions.limit !== void 0) {
3143
3263
  body.limit = this.searchOptions.limit;
3144
3264
  }
@@ -3151,13 +3271,14 @@ var _SearchBuilder = class _SearchBuilder {
3151
3271
  if (this.searchOptions.explain === true) {
3152
3272
  body.explain = true;
3153
3273
  }
3154
- if (this.searchOptions.locale != null && this.searchOptions.locale !== "") {
3155
- body.locale = this.searchOptions.locale;
3274
+ const locale = this.searchOptions.locale ?? this.defaultLocale;
3275
+ if (locale != null && locale !== "") {
3276
+ body.locale = locale;
3156
3277
  }
3157
3278
  const response = await this.httpClient.send(
3158
3279
  "/v1/search",
3159
3280
  "post",
3160
- { body }
3281
+ { body, headers: this.requestHeaders }
3161
3282
  );
3162
3283
  return response.body;
3163
3284
  }
@@ -3177,8 +3298,12 @@ var _SearchBuilder = class _SearchBuilder {
3177
3298
  chunkMWMSFIWN_cjs.__name(_SearchBuilder, "SearchBuilder");
3178
3299
  var SearchBuilder = _SearchBuilder;
3179
3300
  var _AutocompleteBuilder = class _AutocompleteBuilder {
3180
- constructor(httpClient, query) {
3301
+ /**
3302
+ * @param defaultLocale - The client's locale (`withLocale()`), sent when `.locale()` names none
3303
+ */
3304
+ constructor(httpClient, query, defaultLocale) {
3181
3305
  this.httpClient = httpClient;
3306
+ this.defaultLocale = defaultLocale;
3182
3307
  chunkMWMSFIWN_cjs.__publicField(this, "query");
3183
3308
  chunkMWMSFIWN_cjs.__publicField(this, "autocompleteOptions", {});
3184
3309
  this.query = query;
@@ -3257,8 +3382,9 @@ var _AutocompleteBuilder = class _AutocompleteBuilder {
3257
3382
  if (this.autocompleteOptions.limit !== void 0) {
3258
3383
  body.limit = this.autocompleteOptions.limit;
3259
3384
  }
3260
- if (this.autocompleteOptions.locale != null && this.autocompleteOptions.locale !== "") {
3261
- body.locale = this.autocompleteOptions.locale;
3385
+ const locale = this.autocompleteOptions.locale ?? this.defaultLocale;
3386
+ if (locale != null && locale !== "") {
3387
+ body.locale = locale;
3262
3388
  }
3263
3389
  const response = await this.httpClient.send("/v1/search/autocomplete", "post", { body });
3264
3390
  return response.body;
@@ -3273,18 +3399,41 @@ var _AutocompleteBuilder = class _AutocompleteBuilder {
3273
3399
  chunkMWMSFIWN_cjs.__name(_AutocompleteBuilder, "AutocompleteBuilder");
3274
3400
  var AutocompleteBuilder = _AutocompleteBuilder;
3275
3401
  var _SimilarBuilder = class _SimilarBuilder {
3276
- constructor(httpClient, sourceId) {
3402
+ /**
3403
+ * @param defaultLocale - The client's locale (`withLocale()`), sent when `.locale()` names none
3404
+ */
3405
+ constructor(httpClient, sourceId, defaultLocale) {
3277
3406
  this.httpClient = httpClient;
3407
+ this.defaultLocale = defaultLocale;
3278
3408
  chunkMWMSFIWN_cjs.__publicField(this, "sourceId");
3279
3409
  chunkMWMSFIWN_cjs.__publicField(this, "similarOptions", {});
3410
+ chunkMWMSFIWN_cjs.__publicField(this, "requestHeaders");
3280
3411
  this.sourceId = sourceId;
3281
3412
  }
3413
+ /**
3414
+ * Headers this read adds to the client's own, e.g. `X-RelyIO-Dataset` to read another dataset
3415
+ * the key can see. A name the client already sends is replaced for this read only.
3416
+ */
3417
+ headers(headers) {
3418
+ this.requestHeaders = headers;
3419
+ return this;
3420
+ }
3282
3421
  /**
3283
3422
  * Find similar content within a content type
3284
3423
  * @param contentType - Content type to search
3285
3424
  */
3286
3425
  in(contentType) {
3287
3426
  this.similarOptions.contentType = contentType;
3427
+ this.similarOptions.contentTypes = void 0;
3428
+ return this;
3429
+ }
3430
+ /**
3431
+ * Find similar content across several content types, or `'*'` for every type
3432
+ * @param contentTypes - Array of content types or '*' for all
3433
+ */
3434
+ across(contentTypes) {
3435
+ this.similarOptions.contentTypes = contentTypes;
3436
+ this.similarOptions.contentType = void 0;
3288
3437
  return this;
3289
3438
  }
3290
3439
  /**
@@ -3318,26 +3467,33 @@ var _SimilarBuilder = class _SimilarBuilder {
3318
3467
  * Execute the similar query
3319
3468
  */
3320
3469
  async execute() {
3321
- if (this.similarOptions.contentType == null || this.similarOptions.contentType === "") {
3322
- throw new Error('Content type is required for similar search. Use .in("contentType")');
3470
+ const { contentType, contentTypes } = this.similarOptions;
3471
+ if ((contentType == null || contentType === "") && contentTypes == null) {
3472
+ throw new Error(
3473
+ 'Content type is required for similar search. Use .in("contentType") or .across("*")'
3474
+ );
3475
+ }
3476
+ const body = { source: this.sourceId };
3477
+ if (contentType != null && contentType !== "") {
3478
+ body.contentType = contentType;
3479
+ }
3480
+ if (contentTypes != null) {
3481
+ body.contentTypes = contentTypes;
3323
3482
  }
3324
- const body = {
3325
- source: this.sourceId,
3326
- contentType: this.similarOptions.contentType
3327
- };
3328
3483
  if (this.similarOptions.where && Object.keys(this.similarOptions.where).length > 0) {
3329
3484
  body.where = this.similarOptions.where;
3330
3485
  }
3331
3486
  if (this.similarOptions.limit !== void 0) {
3332
3487
  body.limit = this.similarOptions.limit;
3333
3488
  }
3334
- if (this.similarOptions.locale != null && this.similarOptions.locale !== "") {
3335
- body.locale = this.similarOptions.locale;
3489
+ const locale = this.similarOptions.locale ?? this.defaultLocale;
3490
+ if (locale != null && locale !== "") {
3491
+ body.locale = locale;
3336
3492
  }
3337
3493
  const response = await this.httpClient.send(
3338
3494
  "/v1/search/similar",
3339
3495
  "post",
3340
- { body }
3496
+ { body, headers: this.requestHeaders }
3341
3497
  );
3342
3498
  return response.body;
3343
3499
  }
@@ -3357,29 +3513,34 @@ var _SimilarBuilder = class _SimilarBuilder {
3357
3513
  chunkMWMSFIWN_cjs.__name(_SimilarBuilder, "SimilarBuilder");
3358
3514
  var SimilarBuilder = _SimilarBuilder;
3359
3515
  var _SearchClient = class _SearchClient {
3360
- constructor(httpClient) {
3516
+ /**
3517
+ * @param defaultLocale - The client's locale (`withLocale()`): every builder here sends it when
3518
+ * the query names none, as `content()`'s reads do
3519
+ */
3520
+ constructor(httpClient, defaultLocale) {
3361
3521
  this.httpClient = httpClient;
3522
+ this.defaultLocale = defaultLocale;
3362
3523
  }
3363
3524
  /**
3364
3525
  * Start a search query
3365
3526
  * @param query - Search query string
3366
3527
  */
3367
3528
  search(query) {
3368
- return new SearchBuilder(this.httpClient, query);
3529
+ return new SearchBuilder(this.httpClient, query, this.defaultLocale);
3369
3530
  }
3370
3531
  /**
3371
3532
  * Start an autocomplete query
3372
3533
  * @param query - Partial query to autocomplete
3373
3534
  */
3374
3535
  autocomplete(query) {
3375
- return new AutocompleteBuilder(this.httpClient, query);
3536
+ return new AutocompleteBuilder(this.httpClient, query, this.defaultLocale);
3376
3537
  }
3377
3538
  /**
3378
3539
  * Find content similar to a given entry
3379
3540
  * @param sourceId - Entry ID to find similar content for
3380
3541
  */
3381
3542
  similar(sourceId) {
3382
- return new SimilarBuilder(this.httpClient, sourceId);
3543
+ return new SimilarBuilder(this.httpClient, sourceId, this.defaultLocale);
3383
3544
  }
3384
3545
  /**
3385
3546
  * Quick search helper - searches all content types
@@ -3404,6 +3565,7 @@ var _BatchBuilder = class _BatchBuilder {
3404
3565
  returnData: true,
3405
3566
  continueOnError: true
3406
3567
  });
3568
+ chunkMWMSFIWN_cjs.__publicField(this, "requestHeaders");
3407
3569
  }
3408
3570
  // ==========================================================================
3409
3571
  // ENTRY OPERATIONS
@@ -3605,6 +3767,15 @@ var _BatchBuilder = class _BatchBuilder {
3605
3767
  this.defaultOptions.continueOnError = false;
3606
3768
  return this;
3607
3769
  }
3770
+ /**
3771
+ * Headers the batch request adds — e.g. `{ 'X-RelyIO-Dataset': 'staging' }` to write a dataset
3772
+ * other than the client's. They replace the client's own header of the same name for this
3773
+ * request alone, and apply to every operation in it: a batch is one request.
3774
+ */
3775
+ headers(headers) {
3776
+ this.requestHeaders = { ...this.requestHeaders, ...headers };
3777
+ return this;
3778
+ }
3608
3779
  // ==========================================================================
3609
3780
  // EXECUTION
3610
3781
  // ==========================================================================
@@ -3634,6 +3805,7 @@ var _BatchBuilder = class _BatchBuilder {
3634
3805
  const cloned = new _BatchBuilder(this.httpClient);
3635
3806
  cloned.operations = [...this.operations];
3636
3807
  cloned.defaultOptions = { ...this.defaultOptions };
3808
+ cloned.requestHeaders = this.requestHeaders === void 0 ? void 0 : { ...this.requestHeaders };
3637
3809
  return cloned;
3638
3810
  }
3639
3811
  /**
@@ -3651,7 +3823,8 @@ var _BatchBuilder = class _BatchBuilder {
3651
3823
  }
3652
3824
  const finalOptions = { ...this.defaultOptions, ...options };
3653
3825
  return this.httpClient.fetchPayload("/v1/batch", "post", {
3654
- body: { operations: this.operations, options: finalOptions }
3826
+ body: { operations: this.operations, options: finalOptions },
3827
+ headers: this.requestHeaders
3655
3828
  });
3656
3829
  }
3657
3830
  /**
@@ -3731,9 +3904,21 @@ chunkMWMSFIWN_cjs.__name(_BatchClient, "BatchClient");
3731
3904
  var BatchClient = _BatchClient;
3732
3905
 
3733
3906
  // src/queries/adhoc-query-client.ts
3907
+ function withClientLocale(parameters, clientLocale) {
3908
+ if (clientLocale === void 0 || clientLocale === "" || parameters?.locale != null) {
3909
+ return parameters;
3910
+ }
3911
+ return { ...parameters, locale: clientLocale };
3912
+ }
3913
+ chunkMWMSFIWN_cjs.__name(withClientLocale, "withClientLocale");
3734
3914
  var _AdhocQueryBuilder = class _AdhocQueryBuilder {
3735
- constructor(httpClient) {
3915
+ /**
3916
+ * @param defaultLocale - The client's locale (`withLocale()`), sent as the `locale` parameter
3917
+ * when `.param('locale', …)` binds none
3918
+ */
3919
+ constructor(httpClient, defaultLocale) {
3736
3920
  this.httpClient = httpClient;
3921
+ this.defaultLocale = defaultLocale;
3737
3922
  chunkMWMSFIWN_cjs.__publicField(this, "queryDef", {});
3738
3923
  chunkMWMSFIWN_cjs.__publicField(this, "parameters", {});
3739
3924
  chunkMWMSFIWN_cjs.__publicField(this, "options", {});
@@ -3973,8 +4158,9 @@ var _AdhocQueryBuilder = class _AdhocQueryBuilder {
3973
4158
  const body = {
3974
4159
  query: this.queryDef
3975
4160
  };
3976
- if (Object.keys(this.parameters).length > 0) {
3977
- body.parameters = this.parameters;
4161
+ const parameters = withClientLocale(this.parameters, this.defaultLocale);
4162
+ if (parameters !== void 0 && Object.keys(parameters).length > 0) {
4163
+ body.parameters = parameters;
3978
4164
  }
3979
4165
  if (Object.keys(this.options).length > 0) {
3980
4166
  body.options = this.options;
@@ -4005,7 +4191,7 @@ var _AdhocQueryBuilder = class _AdhocQueryBuilder {
4005
4191
  * Clone the builder
4006
4192
  */
4007
4193
  clone() {
4008
- const cloned = new _AdhocQueryBuilder(this.httpClient);
4194
+ const cloned = new _AdhocQueryBuilder(this.httpClient, this.defaultLocale);
4009
4195
  cloned.queryDef = JSON.parse(JSON.stringify(this.queryDef));
4010
4196
  cloned.parameters = { ...this.parameters };
4011
4197
  cloned.options = { ...this.options };
@@ -4015,27 +4201,36 @@ var _AdhocQueryBuilder = class _AdhocQueryBuilder {
4015
4201
  chunkMWMSFIWN_cjs.__name(_AdhocQueryBuilder, "AdhocQueryBuilder");
4016
4202
  var AdhocQueryBuilder = _AdhocQueryBuilder;
4017
4203
  var _AdhocQueryClient = class _AdhocQueryClient {
4018
- constructor(httpClient) {
4204
+ /**
4205
+ * @param defaultLocale - The client's locale (`withLocale()`), sent as the `locale` parameter of
4206
+ * every query that binds none
4207
+ */
4208
+ constructor(httpClient, defaultLocale) {
4019
4209
  this.httpClient = httpClient;
4210
+ this.defaultLocale = defaultLocale;
4020
4211
  }
4021
4212
  /**
4022
4213
  * Create a new query builder
4023
4214
  */
4024
4215
  build() {
4025
- return new AdhocQueryBuilder(this.httpClient);
4216
+ return new AdhocQueryBuilder(this.httpClient, this.defaultLocale);
4026
4217
  }
4027
4218
  /**
4028
4219
  * Execute a query definition directly
4029
4220
  * @param definition - Query definition
4030
4221
  * @param parameters - Parameter values
4031
4222
  * @param options - Execution options
4223
+ * @param request - `headers` this one request adds to the client's own, e.g. `X-RelyIO-Dataset`
4224
+ * to query another dataset the key can see. A name the client already sends is replaced for
4225
+ * this request only.
4032
4226
  */
4033
- async execute(definition, parameters, options) {
4227
+ async execute(definition, parameters, options, request) {
4034
4228
  const body = {
4035
4229
  query: definition
4036
4230
  };
4037
- if (parameters && Object.keys(parameters).length > 0) {
4038
- body.parameters = parameters;
4231
+ const sent = withClientLocale(parameters, this.defaultLocale);
4232
+ if (sent !== void 0 && Object.keys(sent).length > 0) {
4233
+ body.parameters = sent;
4039
4234
  }
4040
4235
  if (options && Object.keys(options).length > 0) {
4041
4236
  body.options = options;
@@ -4043,7 +4238,7 @@ var _AdhocQueryClient = class _AdhocQueryClient {
4043
4238
  return this.httpClient.fetchPayload(
4044
4239
  "/v1/query",
4045
4240
  "post",
4046
- { body }
4241
+ { body, headers: request?.headers }
4047
4242
  );
4048
4243
  }
4049
4244
  /**
@@ -4107,12 +4302,12 @@ async function* pollChanges(http, options = {}) {
4107
4302
  let since = options.since;
4108
4303
  let resynced;
4109
4304
  for (; ; ) {
4110
- if (aborted(signal)) return;
4305
+ if (isAborted(signal)) return;
4111
4306
  let response;
4112
4307
  try {
4113
4308
  response = await pollOnce(http, since, options);
4114
4309
  } catch (error) {
4115
- if (aborted(signal)) return;
4310
+ if (isAborted(signal)) return;
4116
4311
  if (!(error instanceof ResourceExpiredError) || since === void 0) throw error;
4117
4312
  const expired = ChangesExpiredError.of(error);
4118
4313
  const onExpired = options.onExpired ?? "throw";
@@ -4130,11 +4325,11 @@ async function* pollChanges(http, options = {}) {
4130
4325
  const expired = resynced;
4131
4326
  resynced = void 0;
4132
4327
  await options.onExpired(expired);
4133
- if (aborted(signal)) return;
4328
+ if (isAborted(signal)) return;
4134
4329
  }
4135
4330
  for (const change of data) {
4136
4331
  yield change;
4137
- if (aborted(signal)) return;
4332
+ if (isAborted(signal)) return;
4138
4333
  }
4139
4334
  if (data.length === 0) {
4140
4335
  const wait = parseRetryAfterMs(headerValue(response.headers, "Retry-After"), Date.now());
@@ -4143,6 +4338,21 @@ async function* pollChanges(http, options = {}) {
4143
4338
  }
4144
4339
  }
4145
4340
  chunkMWMSFIWN_cjs.__name(pollChanges, "pollChanges");
4341
+ async function readChangesHead(http, options = {}) {
4342
+ const response = await http.send("/v1/changes", "get", {
4343
+ query: {},
4344
+ ...options.signal === void 0 ? {} : { signal: options.signal }
4345
+ });
4346
+ const { data, page } = response.body;
4347
+ if (page.nextCursor === null) {
4348
+ throw new Error("GET /v1/changes answered without page.nextCursor");
4349
+ }
4350
+ if (data.length > 0) {
4351
+ throw new Error("GET /v1/changes without since answered changes; a head carries none");
4352
+ }
4353
+ return page.nextCursor;
4354
+ }
4355
+ chunkMWMSFIWN_cjs.__name(readChangesHead, "readChangesHead");
4146
4356
  async function pollOnce(http, since, options) {
4147
4357
  const query = {};
4148
4358
  if (since !== void 0) query.since = since;
@@ -4156,28 +4366,12 @@ async function pollOnce(http, since, options) {
4156
4366
  });
4157
4367
  }
4158
4368
  chunkMWMSFIWN_cjs.__name(pollOnce, "pollOnce");
4159
- function sleep(ms, signal) {
4160
- return new Promise((resolve) => {
4161
- if (aborted(signal)) {
4162
- resolve();
4163
- return;
4164
- }
4165
- const done = /* @__PURE__ */ chunkMWMSFIWN_cjs.__name(() => {
4166
- clearTimeout(timer);
4167
- signal?.removeEventListener("abort", done);
4168
- resolve();
4169
- }, "done");
4170
- const timer = setTimeout(done, ms);
4171
- signal?.addEventListener("abort", done, { once: true });
4172
- });
4173
- }
4174
- chunkMWMSFIWN_cjs.__name(sleep, "sleep");
4175
- function aborted(signal) {
4176
- return signal?.aborted === true;
4177
- }
4178
- chunkMWMSFIWN_cjs.__name(aborted, "aborted");
4179
4369
 
4180
4370
  // src/config/dev-detection.ts
4371
+ function sandboxStateOf(resolution) {
4372
+ return resolution.state === "active" ? resolution.sandbox : resolution;
4373
+ }
4374
+ chunkMWMSFIWN_cjs.__name(sandboxStateOf, "sandboxStateOf");
4181
4375
  function getProcess() {
4182
4376
  return typeof process !== "undefined" ? process : void 0;
4183
4377
  }
@@ -4201,11 +4395,14 @@ function resolveFromEnv(proc, now) {
4201
4395
  case "active":
4202
4396
  return {
4203
4397
  state: "active",
4204
- override: {
4205
- dataset: verdict.credentials.dataset,
4206
- apiKey: verdict.credentials.apiKey,
4398
+ sandbox: {
4399
+ state: "active",
4400
+ datasetId: verdict.credentials.dataset,
4401
+ branchId: null,
4402
+ expiresAt: verdict.expiresAt,
4207
4403
  source: "env"
4208
- }
4404
+ },
4405
+ apiKey: verdict.credentials.apiKey
4209
4406
  };
4210
4407
  case "expired":
4211
4408
  return { state: "expired", expiredAt: verdict.expiredAt };
@@ -4236,11 +4433,14 @@ function resolveFromFile(proc, now) {
4236
4433
  case "active":
4237
4434
  return {
4238
4435
  state: "active",
4239
- override: {
4240
- dataset: verdict.context.datasetId,
4241
- apiKey: verdict.context.previewApiKey,
4436
+ sandbox: {
4437
+ state: "active",
4438
+ datasetId: verdict.context.datasetId,
4439
+ branchId: verdict.context.branchId,
4440
+ expiresAt: verdict.context.expiresAt,
4242
4441
  source: "file"
4243
- }
4442
+ },
4443
+ apiKey: verdict.context.previewApiKey
4244
4444
  };
4245
4445
  case "expired":
4246
4446
  return { state: "expired", expiredAt: verdict.expiredAt };
@@ -4303,10 +4503,7 @@ var DEFAULT_CONFIG = {
4303
4503
  }
4304
4504
  };
4305
4505
  function validateConfig(config) {
4306
- if (!config.apiKey || config.apiKey.length === 0) {
4307
- throw new Error("API key is required");
4308
- }
4309
- if (typeof config.apiKey !== "string") {
4506
+ if (config.apiKey != null && typeof config.apiKey !== "string") {
4310
4507
  throw new Error("API key must be a string");
4311
4508
  }
4312
4509
  if (config.baseUrl != null && typeof config.baseUrl !== "string") {
@@ -4321,7 +4518,7 @@ function validateConfig(config) {
4321
4518
  }
4322
4519
  chunkMWMSFIWN_cjs.__name(validateConfig, "validateConfig");
4323
4520
  var devOverrideNotified = false;
4324
- function mergeConfig(userConfig) {
4521
+ function resolveConfig(userConfig) {
4325
4522
  validateConfig(userConfig);
4326
4523
  const merged = {
4327
4524
  ...DEFAULT_CONFIG,
@@ -4349,12 +4546,12 @@ function mergeConfig(userConfig) {
4349
4546
  };
4350
4547
  const dev = resolveDevOverride(userConfig.dev);
4351
4548
  if (dev.state === "active") {
4352
- merged.apiKey = dev.override.apiKey;
4353
- merged.dataset = dev.override.dataset;
4549
+ merged.apiKey = dev.apiKey;
4550
+ merged.dataset = dev.sandbox.datasetId;
4354
4551
  if (!devOverrideNotified) {
4355
4552
  devOverrideNotified = true;
4356
4553
  console.info(
4357
- `[RelyIO] Sandbox dev mode active (${dev.override.source}) \u2014 targeting dataset "${dev.override.dataset}" with a scoped preview key.`
4554
+ `[RelyIO] Sandbox dev mode active (${dev.sandbox.source}) \u2014 targeting dataset "${dev.sandbox.datasetId}" with a scoped preview key.`
4358
4555
  );
4359
4556
  }
4360
4557
  } else if (dev.state === "expired" && !devOverrideNotified) {
@@ -4368,9 +4565,9 @@ function mergeConfig(userConfig) {
4368
4565
  `[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.`
4369
4566
  );
4370
4567
  }
4371
- return merged;
4568
+ return { config: merged, sandbox: sandboxStateOf(dev) };
4372
4569
  }
4373
- chunkMWMSFIWN_cjs.__name(mergeConfig, "mergeConfig");
4570
+ chunkMWMSFIWN_cjs.__name(resolveConfig, "resolveConfig");
4374
4571
 
4375
4572
  // src/client/create-client.ts
4376
4573
  var API_VERSION_SEGMENT = "v1";
@@ -4378,17 +4575,20 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
4378
4575
  constructor(config) {
4379
4576
  chunkMWMSFIWN_cjs.__publicField(this, "httpClient");
4380
4577
  chunkMWMSFIWN_cjs.__publicField(this, "config");
4578
+ chunkMWMSFIWN_cjs.__publicField(this, "_sandbox");
4381
4579
  chunkMWMSFIWN_cjs.__publicField(this, "_search");
4382
4580
  chunkMWMSFIWN_cjs.__publicField(this, "_batch");
4383
4581
  chunkMWMSFIWN_cjs.__publicField(this, "_adhocQuery");
4384
4582
  chunkMWMSFIWN_cjs.__publicField(this, "_schema");
4385
4583
  const { schema, ...clientConfig } = config;
4386
4584
  this._schema = schema;
4387
- this.config = mergeConfig(clientConfig);
4585
+ const resolved = resolveConfig(clientConfig);
4586
+ this.config = resolved.config;
4587
+ this._sandbox = resolved.sandbox;
4388
4588
  this.httpClient = new HttpClient(this.config);
4389
- this._search = new SearchClient(this.httpClient);
4589
+ this._search = new SearchClient(this.httpClient, this.config.locale);
4390
4590
  this._batch = new BatchClient(this.httpClient);
4391
- this._adhocQuery = new AdhocQueryClient(this.httpClient);
4591
+ this._adhocQuery = new AdhocQueryClient(this.httpClient, this.config.locale);
4392
4592
  }
4393
4593
  // ============================================================================
4394
4594
  // CORE CLIENTS
@@ -4399,6 +4599,23 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
4399
4599
  get schema() {
4400
4600
  return this._schema;
4401
4601
  }
4602
+ /**
4603
+ * The `relyio dev` sandbox this client resolved at construction, as the preview toolbar displays
4604
+ * it (`sdk-surfaces-spec.md` decision 22).
4605
+ *
4606
+ * Plain, JSON-serializable data and no key, so a server component can hand it to a client
4607
+ * component as a prop — the browser cannot read the handshake itself, so a client created there
4608
+ * reports `absent`. See {@link SandboxState} for what each state means.
4609
+ *
4610
+ * @example
4611
+ * ```tsx
4612
+ * // app/layout.tsx — a server component handing the state to a client component
4613
+ * <SandboxBadge sandbox={relyio.sandbox} />
4614
+ * ```
4615
+ */
4616
+ get sandbox() {
4617
+ return this._sandbox;
4618
+ }
4402
4619
  /**
4403
4620
  * Get the ad-hoc query client
4404
4621
  */
@@ -4459,7 +4676,7 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
4459
4676
  * ```
4460
4677
  */
4461
4678
  singleton(key) {
4462
- return new SingletonClient(this.httpClient, key);
4679
+ return new SingletonClient(this.httpClient, key, this.config.locale);
4463
4680
  }
4464
4681
  /**
4465
4682
  * Get a settings client for a specific settings group
@@ -4473,13 +4690,16 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
4473
4690
  * ```
4474
4691
  */
4475
4692
  settings(groupId) {
4476
- return new SettingsClient(this.httpClient, groupId);
4693
+ return new SettingsClient(this.httpClient, groupId, this.config.locale);
4477
4694
  }
4478
4695
  // ============================================================================
4479
4696
  // SEARCH OPERATIONS
4480
4697
  // ============================================================================
4481
4698
  /**
4482
- * Start a search query with full-text, semantic, or hybrid search
4699
+ * Start a search query with full-text, semantic, or hybrid search.
4700
+ *
4701
+ * Where a schema types the client, the results are its entries — every content type's until
4702
+ * `in()` or `across()` narrows them (`INV-SS-TYPES-INFERRED`).
4483
4703
  *
4484
4704
  * @example
4485
4705
  * ```typescript
@@ -4577,6 +4797,25 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
4577
4797
  changes(options = {}) {
4578
4798
  return pollChanges(this.httpClient, options);
4579
4799
  }
4800
+ /**
4801
+ * The dataset's current position in the changes feed, as a cursor to start
4802
+ * `changes({ since })` or `followChanges(…, { since })` from. One
4803
+ * `GET /v1/changes` without a cursor: answered at once, holding nothing.
4804
+ *
4805
+ * Take it **before** you read what you will follow. A write your read did not
4806
+ * see is then after the position and replayed; opening the feed afterwards
4807
+ * without a `since` starts after it and misses it.
4808
+ *
4809
+ * @example
4810
+ * ```typescript
4811
+ * const since = await relyio.changesHead()
4812
+ * const post = await relyio.content('post').findById(id).execute()
4813
+ * for await (const change of relyio.changes({ since, ids: [id] })) { … }
4814
+ * ```
4815
+ */
4816
+ changesHead(options = {}) {
4817
+ return readChangesHead(this.httpClient, options);
4818
+ }
4580
4819
  // ============================================================================
4581
4820
  // QUERY OPERATIONS
4582
4821
  // ============================================================================
@@ -4614,14 +4853,33 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
4614
4853
  * ```
4615
4854
  */
4616
4855
  async executeQuery(queryName, params) {
4856
+ const clientLocale = this.config.locale;
4857
+ const namesLocale = params?.locale != null || params?.locales != null;
4617
4858
  const response = await this.httpClient.request({
4618
4859
  method: "GET",
4619
4860
  url: `/${API_VERSION_SEGMENT}/${String(queryName)}`,
4620
- params: params ?? {}
4861
+ params: namesLocale || clientLocale === void 0 || clientLocale === "" ? params ?? {} : { ...params, locale: clientLocale }
4621
4862
  });
4622
4863
  const payload = readPayload(response.body);
4623
4864
  return Array.isArray(payload) ? payload : [payload];
4624
4865
  }
4866
+ /**
4867
+ * Describe the project this key resolves to — `GET /v1/project`.
4868
+ *
4869
+ * `dataset` is the one this client's requests select (`dataset`, a sandbox from the `relyio dev`
4870
+ * handshake, or the key's default), which is not always the project's default, so a Studio link
4871
+ * built from `studioUrl` should carry it as `?dataset=`. `studioUrl` is `null` — a value, not an
4872
+ * error — when the service has no Dashboard address configured.
4873
+ *
4874
+ * @example
4875
+ * ```typescript
4876
+ * const { studioUrl, dataset } = await relyio.project()
4877
+ * if (studioUrl !== null) window.open(`${studioUrl}?dataset=${dataset.id}`)
4878
+ * ```
4879
+ */
4880
+ async project() {
4881
+ return this.httpClient.fetchPayload("/v1/project", "get", {});
4882
+ }
4625
4883
  /**
4626
4884
  * Get schema information from the API
4627
4885
  */
@@ -4668,11 +4926,13 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
4668
4926
  * Update the configuration and reinitialize all clients
4669
4927
  */
4670
4928
  updateConfig(newConfig) {
4671
- this.config = mergeConfig({ ...this.config, ...newConfig });
4929
+ const resolved = resolveConfig({ ...this.config, ...newConfig });
4930
+ this.config = resolved.config;
4931
+ this._sandbox = resolved.sandbox;
4672
4932
  this.httpClient = new HttpClient(this.config);
4673
- this._search = new SearchClient(this.httpClient);
4933
+ this._search = new SearchClient(this.httpClient, this.config.locale);
4674
4934
  this._batch = new BatchClient(this.httpClient);
4675
- this._adhocQuery = new AdhocQueryClient(this.httpClient);
4935
+ this._adhocQuery = new AdhocQueryClient(this.httpClient, this.config.locale);
4676
4936
  }
4677
4937
  /**
4678
4938
  * Get the current configuration
@@ -4698,7 +4958,10 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
4698
4958
  // ============================================================================
4699
4959
  /**
4700
4960
  * Create a locale-scoped client
4701
- * All content operations on the returned client will use the specified locale
4961
+ *
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.
4702
4965
  *
4703
4966
  * Type-safe when schema is provided: autocompletes valid locale codes!
4704
4967
  *
@@ -4706,9 +4969,10 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
4706
4969
  * ```typescript
4707
4970
  * const spanish = relyio.withLocale('es-ES') // ← Autocompletes with schema!
4708
4971
  *
4709
- * // All operations use Spanish
4972
+ * // Reads use Spanish
4710
4973
  * const products = await spanish.content('product').query().execute()
4711
4974
  * const homepage = await spanish.singleton('homepage').fetch()
4975
+ * const results = await spanish.search('zapatos').execute()
4712
4976
  *
4713
4977
  * // Original client unchanged
4714
4978
  * const englishProducts = await relyio.content('product').query().execute()
@@ -4816,9 +5080,12 @@ exports.UsageQuotaExceededError = UsageQuotaExceededError;
4816
5080
  exports.ValidationError = ValidationError;
4817
5081
  exports.createClient = createClient;
4818
5082
  exports.entityTagOf = entityTagOf;
5083
+ exports.isAborted = isAborted;
4819
5084
  exports.isAssetEntry = isAssetEntry;
4820
5085
  exports.isResourceNotFound = isResourceNotFound;
4821
5086
  exports.matchRoute = matchRoute;
5087
+ exports.serviceInstructedDelayMs = serviceInstructedDelayMs;
5088
+ exports.sleep = sleep;
4822
5089
  exports.validateFile = validateFile;
4823
- //# sourceMappingURL=chunk-XS32U2UP.cjs.map
4824
- //# sourceMappingURL=chunk-XS32U2UP.cjs.map
5090
+ //# sourceMappingURL=chunk-GCMQUM6N.cjs.map
5091
+ //# sourceMappingURL=chunk-GCMQUM6N.cjs.map