@relyio/sdk 0.13.0 → 0.14.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 (36) hide show
  1. package/CHANGELOG.md +144 -0
  2. package/dist/{chunk-3NPPMGB6.js → chunk-5UMY6CS4.js} +2 -2
  3. package/dist/{chunk-6NGJXLOR.js → chunk-DBBZVZOS.js} +968 -2681
  4. package/dist/{chunk-DEDIJKHY.cjs → chunk-GUPV2M3X.cjs} +969 -2691
  5. package/dist/{chunk-EW23SZPX.js → chunk-PCFTJLEP.js} +540 -262
  6. package/dist/{chunk-Z6GLWSSD.cjs → chunk-RSHMA4KT.cjs} +2 -2
  7. package/dist/{chunk-FOSM4LMM.cjs → chunk-X4KSS7PP.cjs} +539 -262
  8. package/dist/client/index.cjs +3 -3
  9. package/dist/client/index.d.cts +3 -3
  10. package/dist/client/index.d.ts +3 -3
  11. package/dist/client/index.js +1 -1
  12. package/dist/{include-paths-Bs5FhNw2.d.cts → define-content-type-BUZeWtpk.d.cts} +3657 -2594
  13. package/dist/{include-paths-Cb7iPCW2.d.ts → define-content-type-DOrZb_a_.d.ts} +3657 -2594
  14. package/dist/{define-schema-UHViXKiv.d.ts → define-schema-5aHmsA8j.d.ts} +238 -356
  15. package/dist/{define-schema-sWJ8Zn7C.d.cts → define-schema-nCfZeboB.d.cts} +238 -356
  16. package/dist/{index-8DozAWsA.d.ts → index-B97ZDj44.d.ts} +2 -2
  17. package/dist/{index--_u1nPHv.d.cts → index-C-pcllqY.d.cts} +2 -2
  18. package/dist/index-DOhFQbv7.d.ts +3065 -0
  19. package/dist/index-DgVV5Nj7.d.cts +3065 -0
  20. package/dist/index.cjs +90 -130
  21. package/dist/index.d.cts +16 -23
  22. package/dist/index.d.ts +16 -23
  23. package/dist/index.js +4 -4
  24. package/dist/rich-text/index.cjs +11 -11
  25. package/dist/rich-text/index.d.cts +2 -2
  26. package/dist/rich-text/index.d.ts +2 -2
  27. package/dist/rich-text/index.js +1 -1
  28. package/dist/schema/index.cjs +33 -37
  29. package/dist/schema/index.d.cts +33 -18
  30. package/dist/schema/index.d.ts +33 -18
  31. package/dist/schema/index.js +1 -1
  32. package/dist/types/index.d.cts +1 -1
  33. package/dist/types/index.d.ts +1 -1
  34. package/package.json +2 -2
  35. package/dist/index-BT0L8xJj.d.cts +0 -3717
  36. package/dist/index-CPjJ25R4.d.ts +0 -3717
@@ -302,6 +302,10 @@ function isResourceNotFound(error) {
302
302
  return error instanceof ResourceNotFoundError;
303
303
  }
304
304
  __name(isResourceNotFound, "isResourceNotFound");
305
+ function isMissingEntry(error) {
306
+ return error instanceof ResourceNotFoundError && error.context?.resourceType === "entry";
307
+ }
308
+ __name(isMissingEntry, "isMissingEntry");
305
309
 
306
310
  // src/errors/network-errors.ts
307
311
  var _NetworkError = class _NetworkError extends SDKError {
@@ -461,9 +465,12 @@ __name(refuseMismatchedAlternate, "refuseMismatchedAlternate");
461
465
 
462
466
  // ../../platform/api-contract/dist/index.js
463
467
  var HEADER_RETRY_AFTER = { name: "Retry-After" };
464
- var HEADER_ENTITY_TAG = { name: "X-Entity-Tag" };
465
468
  var HEADER_IF_MATCH = {
466
469
  name: "If-Match"};
470
+ function entityTag(parts) {
471
+ return `"${parts.entryUid}:${parts.locale}:${parts.version}:${parts.sharedVersion}"`;
472
+ }
473
+ __name(entityTag, "entityTag");
467
474
  var HEADER_IDEMPOTENCY_KEY = {
468
475
  name: "Idempotency-Key"};
469
476
  var FRESH_PARAMETER = "_fresh";
@@ -698,6 +705,7 @@ function parseRetryAfterMs(value, nowMs) {
698
705
  __name(parseRetryAfterMs, "parseRetryAfterMs");
699
706
 
700
707
  // src/errors/error-mapper.ts
708
+ var STALE_VERSION_GUIDANCE = "The entry changed since it was read: re-read it with `fresh: true` and retry the write with what that read returns.";
701
709
  var _ErrorMapper = class _ErrorMapper {
702
710
  /** Map a non-2xx response to an SDK error. */
703
711
  static mapHttpError(response, requestContext) {
@@ -765,10 +773,16 @@ var _ErrorMapper = class _ErrorMapper {
765
773
  });
766
774
  }
767
775
  case "RESOURCE_CONFLICT": {
768
- return new ResourceConflictError(message, 409, void 0, {
776
+ const expectedVersion = detailNumber(refusal, "expectedVersion");
777
+ const guidance = expectedVersion === void 0 ? message : `${message.endsWith(".") ? message : `${message}.`} ${STALE_VERSION_GUIDANCE}`;
778
+ return new ResourceConflictError(guidance, 409, void 0, {
769
779
  ...context,
770
- expectedVersion: detailNumber(refusal, "expectedVersion"),
771
- actualVersion: detailNumber(refusal, "actualVersion")
780
+ expectedVersion,
781
+ actualVersion: detailNumber(refusal, "actualVersion"),
782
+ // The shared fields' counter, which the `If-Match` tag carries beside the locale's and a
783
+ // write that changes a shared field is checked against (REL-2363).
784
+ expectedSharedVersion: detailNumber(refusal, "expectedSharedVersion"),
785
+ actualSharedVersion: detailNumber(refusal, "actualSharedVersion")
772
786
  });
773
787
  }
774
788
  case "RESOURCE_EXPIRED": {
@@ -930,6 +944,7 @@ var DECLARED_PATHS = {
930
944
  "/v1/meta/schema/{contentType}": true,
931
945
  "/v1/meta/schema/{contentType}/fields": true,
932
946
  "/v1/project": true,
947
+ "/v1/queries/{name}": true,
933
948
  "/v1/query": true,
934
949
  "/v1/search": true,
935
950
  "/v1/search/autocomplete": true,
@@ -1092,9 +1107,27 @@ var _HttpClient = class _HttpClient {
1092
1107
  constructor(config) {
1093
1108
  /** What every request reads off the configuration, derived by {@link configure} and nowhere else. */
1094
1109
  __publicField(this, "connection");
1110
+ /**
1111
+ * Set on a view {@link withHeaders} made: the client every request is sent through, and the
1112
+ * headers laid beneath each request's own. A view keeps no connection of its own that matters —
1113
+ * everything it is asked to change, it changes on that client.
1114
+ */
1115
+ __publicField(this, "view");
1095
1116
  this.connection = connectionFor(config);
1096
1117
  this.announce();
1097
1118
  }
1119
+ /**
1120
+ * A view of this client that sends every request through it with `headers` added — beneath the
1121
+ * request's own, over this client's — and changes nothing else: its key, base URL, timeout,
1122
+ * retries and `fetch` are this client's as they are when each request is sent, so a later
1123
+ * `configure()` or `setApiKey()` on this client reaches the view, and one on the view reaches
1124
+ * this client. `client.with({ dataset })` is one (REL-2417).
1125
+ */
1126
+ withHeaders(headers) {
1127
+ const view = new _HttpClient(this.connection.config);
1128
+ view.view = { client: this, headers: { ...headers } };
1129
+ return view;
1130
+ }
1098
1131
  /**
1099
1132
  * Run on `config` from now on: every later request — whoever holds this client — is sent to its
1100
1133
  * `baseUrl`, with its headers, key and dataset, its timeout and retries, through its `fetch`, and
@@ -1106,6 +1139,7 @@ var _HttpClient = class _HttpClient {
1106
1139
  * replaced by what `config` says.
1107
1140
  */
1108
1141
  configure(config) {
1142
+ if (this.view !== void 0) return this.view.client.configure(config);
1109
1143
  this.connection = connectionFor(config);
1110
1144
  this.announce();
1111
1145
  }
@@ -1164,6 +1198,12 @@ var _HttpClient = class _HttpClient {
1164
1198
  * given none.
1165
1199
  */
1166
1200
  async request(config) {
1201
+ if (this.view !== void 0) {
1202
+ return this.view.client.request({
1203
+ ...config,
1204
+ headers: layerHeaders(this.view.headers, config.headers)
1205
+ });
1206
+ }
1167
1207
  if (config.timeout !== void 0) assertRequestTimeout(config.timeout);
1168
1208
  const mergedConfig = this.mergeRequestConfig(config);
1169
1209
  if (presentKey(headerValue(mergedConfig.headers, API_KEY_HEADER)) === void 0) {
@@ -1274,6 +1314,7 @@ var _HttpClient = class _HttpClient {
1274
1314
  * matched without regard to case — until the client is next configured.
1275
1315
  */
1276
1316
  setDefaultHeaders(headers) {
1317
+ if (this.view !== void 0) return this.view.client.setDefaultHeaders(headers);
1277
1318
  this.connection = {
1278
1319
  ...this.connection,
1279
1320
  defaultHeaders: layerHeaders(this.connection.defaultHeaders, headers)
@@ -1297,6 +1338,7 @@ var _HttpClient = class _HttpClient {
1297
1338
  * never touched: the API reads `X-API-Key` first, so the old key was the one used (REL-1385).
1298
1339
  */
1299
1340
  setApiKey(apiKey) {
1341
+ if (this.view !== void 0) return this.view.client.setApiKey(apiKey);
1300
1342
  const { config } = this.connection;
1301
1343
  config.apiKey = apiKey;
1302
1344
  config.headers = layerHeaders(config.headers, { [API_KEY_HEADER]: null });
@@ -1311,18 +1353,23 @@ var _HttpClient = class _HttpClient {
1311
1353
  * Get current default headers
1312
1354
  */
1313
1355
  getDefaultHeaders() {
1356
+ if (this.view !== void 0) {
1357
+ return layerHeaders(this.view.client.getDefaultHeaders(), this.view.headers);
1358
+ }
1314
1359
  return { ...this.connection.defaultHeaders };
1315
1360
  }
1316
1361
  /**
1317
1362
  * Update base URL
1318
1363
  */
1319
1364
  setBaseUrl(baseUrl) {
1365
+ if (this.view !== void 0) return this.view.client.setBaseUrl(baseUrl);
1320
1366
  this.connection = { ...this.connection, baseUrl };
1321
1367
  }
1322
1368
  /**
1323
1369
  * Get current base URL
1324
1370
  */
1325
1371
  getBaseUrl() {
1372
+ if (this.view !== void 0) return this.view.client.getBaseUrl();
1326
1373
  return this.connection.baseUrl;
1327
1374
  }
1328
1375
  /**
@@ -1590,6 +1637,31 @@ function credentialsModeFor(includeCredentials) {
1590
1637
  }
1591
1638
  __name(credentialsModeFor, "credentialsModeFor");
1592
1639
 
1640
+ // src/http/write-options.ts
1641
+ function versionCheckOf(entry) {
1642
+ const { id, locale, version, sharedVersion } = entry._metadata;
1643
+ return { id, locale, ifMatch: entityTag({ entryUid: id, locale, version, sharedVersion }) };
1644
+ }
1645
+ __name(versionCheckOf, "versionCheckOf");
1646
+ function mintIdempotencyKey() {
1647
+ const webCrypto = globalThis.crypto;
1648
+ if (typeof webCrypto?.randomUUID !== "function") return void 0;
1649
+ return webCrypto.randomUUID();
1650
+ }
1651
+ __name(mintIdempotencyKey, "mintIdempotencyKey");
1652
+ function writeHeaders(options) {
1653
+ const headers = {};
1654
+ if (options?.ifMatch !== void 0) {
1655
+ headers[HEADER_IF_MATCH.name] = options.ifMatch;
1656
+ }
1657
+ const idempotencyKey = options?.idempotencyKey ?? mintIdempotencyKey();
1658
+ if (idempotencyKey !== void 0) {
1659
+ headers[HEADER_IDEMPOTENCY_KEY.name] = idempotencyKey;
1660
+ }
1661
+ return Object.keys(headers).length > 0 ? headers : void 0;
1662
+ }
1663
+ __name(writeHeaders, "writeHeaders");
1664
+
1593
1665
  // src/content/client-locale.ts
1594
1666
  function callLocale(named, clientLocale) {
1595
1667
  if (named !== void 0) return named;
@@ -1601,7 +1673,12 @@ function readClientLocale(locale) {
1601
1673
  }
1602
1674
  __name(readClientLocale, "readClientLocale");
1603
1675
 
1604
- // src/content/query-builder.ts
1676
+ // src/content/read-terms.ts
1677
+ function writeChildrenQuery(params, options) {
1678
+ params.children = options.depth === void 0 ? "true" : `${options.depth}`;
1679
+ if (options.limit !== void 0) params.childrenLimit = options.limit;
1680
+ }
1681
+ __name(writeChildrenQuery, "writeChildrenQuery");
1605
1682
  function namesMetadata(key) {
1606
1683
  return key === "_metadata" || key.startsWith("_metadata.");
1607
1684
  }
@@ -1619,898 +1696,309 @@ function refuseMetadataConditions(conditions) {
1619
1696
  );
1620
1697
  }
1621
1698
  __name(refuseMetadataConditions, "refuseMetadataConditions");
1622
- var _ContentQueryBuilder = class _ContentQueryBuilder {
1623
- /**
1624
- * @param locale - The client's locale (`withLocale()`, `updateConfig({ locale })`), sent when the
1625
- * query names none: the function `TypedRelyIOClient` hands over, or a locale fixed for it
1626
- */
1627
- constructor(httpClient, contentType, locale) {
1628
- this.httpClient = httpClient;
1629
- this.contentType = contentType;
1630
- __publicField(this, "queryOptions", {});
1631
- __publicField(this, "localeValue");
1632
- __publicField(this, "localesArray");
1633
- __publicField(this, "fetchAllLocales", false);
1634
- __publicField(this, "fieldsProjection");
1635
- __publicField(this, "childrenOptions");
1636
- __publicField(this, "pageNumber");
1637
- __publicField(this, "pageSizeValue");
1638
- __publicField(this, "requestHeaders");
1639
- /** The client's locale, asked when the query is sent rather than copied when it was built. */
1640
- __publicField(this, "clientLocale");
1641
- __publicField(this, "freshValue");
1642
- this.clientLocale = readClientLocale(locale);
1643
- }
1644
- /**
1645
- * The feed position this read follows — a change's `fresh` (`FeedChange`), or a resync's. Sent as
1646
- * `?_fresh`, the one term the API declares for it: no cache — the Content API's edge or the
1647
- * browser's own — holds an answer for that position from before it, so the read is answered after
1648
- * the change, and every read naming the same position shares that answer. The answer is the
1649
- * read's own; omit it for a read no change prompted.
1650
- */
1651
- fresh(position) {
1652
- this.freshValue = position;
1653
- return this;
1654
- }
1655
- /**
1656
- * Headers this query adds to the client's own, e.g. `X-RelyIO-Dataset` to list another dataset
1657
- * the key can see. A name the client already sends is replaced for this query only; the client
1658
- * is not changed.
1659
- */
1660
- headers(headers) {
1661
- this.requestHeaders = headers;
1662
- return this;
1663
- }
1664
- /**
1665
- * Add where conditions to the query, each naming a value by its path: a field the content type
1666
- * declares, a field inside an object (`'pricing.basePrice'`), a reference inside its blocks —
1667
- * `{ 'sections.features': featureId }` keeps the entries some block of which holds that feature
1668
- * (REL-1810) — and rich text that can point at an entry, by the entries it points at. A typed read
1669
- * takes exactly the paths the API filters by (`FilterPaths`, REL-2136). A condition on `_metadata`
1670
- * is refused here, before anything is sent (`refuseMetadataConditions`).
1671
- */
1672
- where(conditions) {
1673
- refuseMetadataConditions(conditions);
1674
- this.queryOptions.where = {
1675
- ...this.queryOptions.where,
1676
- // Sent as written: a path into an object or a block, or a rich-text field's condition, rides
1677
- // beside the fields', which `QueryOptions` types by the record's own keys alone
1678
- ...conditions
1679
- };
1680
- return this;
1681
- }
1682
- /**
1683
- * Select specific fields to return
1684
- */
1685
- select(fields) {
1686
- this.queryOptions.select = fields;
1687
- return this;
1688
- }
1689
- /**
1690
- * Expand references into the entries they point at: one include path per element (REL-1801).
1691
- * A path names fields through lists, page builders, objects and references —
1692
- * `'sections.features'`, `'sections.customers.logo'` — and may pick the fields each entry returns,
1693
- * `'sections.features(name,slug)'`. Each is checked against the schema: a path that holds no
1694
- * reference does not compile, and the read's type follows the paths — an expanded reference is the
1695
- * entry, every other one its id.
1696
- */
1697
- include(paths) {
1698
- this.queryOptions.include = [
1699
- ...this.queryOptions.include ?? [],
1700
- ...paths
1701
- ];
1702
- return this;
1699
+
1700
+ // src/content/read-options.ts
1701
+ function listQuery(options, clientLocale) {
1702
+ const query = {};
1703
+ if (options.where !== void 0) {
1704
+ refuseMetadataConditions(options.where);
1705
+ if (Object.keys(options.where).length > 0) query.filters = JSON.stringify(options.where);
1703
1706
  }
1704
- sort(fieldOrOptions, direction) {
1705
- if (typeof fieldOrOptions === "string") {
1706
- this.queryOptions.sort = {
1707
- ...this.queryOptions.sort,
1708
- [fieldOrOptions]: direction ?? "asc"
1709
- };
1710
- } else {
1711
- this.queryOptions.sort = {
1712
- ...this.queryOptions.sort,
1713
- ...fieldOrOptions
1714
- };
1715
- }
1716
- return this;
1707
+ if (options.select !== void 0 && options.select.length > 0) {
1708
+ query.fields = options.select.join(",");
1717
1709
  }
1718
- /**
1719
- * Sort by a specific field in ascending order
1720
- */
1721
- sortAsc(field) {
1722
- return this.sort(field, "asc");
1710
+ if (options.sort !== void 0) {
1711
+ const order = Object.entries(options.sort).flatMap(
1712
+ ([key, direction]) => direction === void 0 ? [] : [`${key}:${direction}`]
1713
+ );
1714
+ if (order.length > 0) query.sort = order.join(",");
1723
1715
  }
1724
- /**
1725
- * Sort by a specific field in descending order
1726
- */
1727
- sortDesc(field) {
1728
- return this.sort(field, "desc");
1716
+ if (options.limit !== void 0) query.limit = options.limit;
1717
+ if (options.cursor !== void 0) query.cursor = options.cursor;
1718
+ if (options.page !== void 0) query.page = options.page;
1719
+ writeReadTerms(query, options, clientLocale);
1720
+ return query;
1721
+ }
1722
+ __name(listQuery, "listQuery");
1723
+ function entryQuery(options, clientLocale) {
1724
+ const query = {};
1725
+ writeReadTerms(query, options, clientLocale);
1726
+ return query;
1727
+ }
1728
+ __name(entryQuery, "entryQuery");
1729
+ function documentQuery(options, clientLocale) {
1730
+ const query = {};
1731
+ writeDocumentTerms(query, options, clientLocale);
1732
+ return query;
1733
+ }
1734
+ __name(documentQuery, "documentQuery");
1735
+ function writeReadTerms(query, options, clientLocale) {
1736
+ writeDocumentTerms(query, options, clientLocale);
1737
+ if (options.children !== void 0) {
1738
+ writeChildrenQuery(query, options.children === true ? {} : options.children);
1729
1739
  }
1730
- /**
1731
- * Limit the number of results
1732
- */
1733
- limit(count) {
1734
- this.queryOptions.limit = count;
1735
- return this;
1740
+ }
1741
+ __name(writeReadTerms, "writeReadTerms");
1742
+ function writeDocumentTerms(query, options, clientLocale) {
1743
+ if (options.include !== void 0 && options.include.length > 0) {
1744
+ query.include = options.include.join(",");
1736
1745
  }
1737
- /**
1738
- * Set offset for pagination
1739
- */
1740
- offset(count) {
1741
- this.queryOptions.offset = count;
1742
- return this;
1746
+ writeLocale(query, options.locale ?? clientLocale);
1747
+ if (typeof options.fresh === "string") query[FRESH_PARAMETER] = options.fresh;
1748
+ }
1749
+ __name(writeDocumentTerms, "writeDocumentTerms");
1750
+ function writeLocale(query, locale) {
1751
+ if (locale === void 0) return;
1752
+ if (typeof locale === "string") {
1753
+ if (locale !== "") query.locale = locale;
1754
+ return;
1755
+ }
1756
+ if (locale.length === 0) {
1757
+ throw new ValidationError(
1758
+ "`locale` is an empty list, which names no locale to read. Name one code, a list of codes or '*'.",
1759
+ [{ field: "locale", code: "EMPTY_LOCALE_LIST", message: "locale names no code" }]
1760
+ );
1743
1761
  }
1762
+ query.locales = locale.join(",");
1763
+ }
1764
+ __name(writeLocale, "writeLocale");
1765
+ function readHeaders(options) {
1766
+ const headers = {};
1767
+ if (options.dataset !== void 0) headers[DATASET_HEADER] = options.dataset;
1768
+ if (options.fresh === true) headers["Cache-Control"] = "no-cache";
1769
+ return Object.keys(headers).length > 0 ? headers : void 0;
1770
+ }
1771
+ __name(readHeaders, "readHeaders");
1772
+
1773
+ // src/content/entry-handle.ts
1774
+ var _EntryHandle = class _EntryHandle {
1744
1775
  /**
1745
- * Set cursor for cursor-based pagination
1776
+ * @param typeName - The content or asset type the routes are addressed by
1777
+ * @param locale - The client's locale: the function `TypedRelyIOClient` hands over, asked on
1778
+ * every call
1746
1779
  */
1747
- cursor(cursor) {
1748
- this.queryOptions.cursor = cursor;
1749
- return this;
1780
+ constructor(httpClient, typeName, locale) {
1781
+ this.httpClient = httpClient;
1782
+ this.typeName = typeName;
1783
+ /** The client's locale, asked on every call rather than copied when this handle was taken. */
1784
+ __publicField(this, "clientLocale");
1785
+ this.clientLocale = readClientLocale(locale);
1750
1786
  }
1751
1787
  /**
1752
- * Set locale for localized content
1753
- * @param locale - Locale code (e.g., 'en-US', 'de-DE', 'fr-FR')
1788
+ * One page of this type's entries: the API's own `{ data, page }`, as it answered (REL-2415).
1754
1789
  *
1755
- * @example
1756
- * ```typescript
1757
- * // Fetch in Spanish
1758
- * .locale('es-ES')
1759
- * ```
1760
- */
1761
- locale(locale) {
1762
- this.localeValue = locale;
1763
- this.localesArray = void 0;
1764
- this.fetchAllLocales = false;
1765
- return this;
1766
- }
1767
- /**
1768
- * Fetch content in multiple specific locales
1769
- * Returns primary locale at top level, others in _metadata.localizations
1790
+ * Page by `cursor` — the `page.nextCursor` a page handed back — to walk, or by `page` number for an
1791
+ * archive page like `/blog/page/3`; `limit` is the page size for both. The page is the cursor
1792
+ * dialect's `{ nextCursor, hasMore }`: a list carries no total.
1770
1793
  *
1771
- * @param localeCodes - Array of locale codes to fetch
1794
+ * The rest of the options have one name per concern (REL-2417): `select` the entry's own Fields,
1795
+ * `sort` an order whose key order is the priority, `locale` a code, a list of codes or `'*'`,
1796
+ * `dataset` this read's dataset, `fresh` how fresh the answer must be, `children` the children to
1797
+ * embed. See `ListReadOptions`.
1772
1798
  *
1773
1799
  * @example
1774
1800
  * ```typescript
1775
- * const product = await client.content('product')
1776
- * .findById('prod_123')
1777
- * .locales(['en-US', 'es-ES', 'de-DE'])
1778
- * .execute()
1779
- *
1780
- * // Access: product.title (first locale)
1781
- * // Access: product._metadata.localizations['es-ES'].title
1801
+ * const { data, page } = await relyio.content('post').list({
1802
+ * where: { category: 'news' },
1803
+ * include: ['author'],
1804
+ * select: ['title', 'slug'],
1805
+ * sort: { featured: 'desc', publishedAt: 'desc' },
1806
+ * limit: 10,
1807
+ * page: 3,
1808
+ * })
1782
1809
  * ```
1783
1810
  */
1784
- locales(localeCodes) {
1785
- this.localesArray = localeCodes;
1786
- this.localeValue = void 0;
1787
- this.fetchAllLocales = false;
1788
- return this;
1811
+ async list(options = {}) {
1812
+ const response = await this.httpClient.send(
1813
+ "/v1/{contentType}",
1814
+ "get",
1815
+ {
1816
+ path: { contentType: this.typeName },
1817
+ query: listQuery(options, this.clientLocale()),
1818
+ headers: readHeaders(options)
1819
+ }
1820
+ );
1821
+ return response.body;
1789
1822
  }
1790
1823
  /**
1791
- * Fetch content in all available locales
1792
- * Returns default locale at top level, others in _metadata.localizations
1793
- *
1794
- * @example
1795
- * ```typescript
1796
- * const product = await client.content('product')
1797
- * .findById('prod_123')
1798
- * .allLocales()
1799
- * .execute()
1824
+ * Every entry of this type that `where` keeps, page after page, as an async iterable: the walk
1825
+ * follows each page's cursor and stops where the API says there is no more (REL-2415).
1826
+ * `Array.fromAsync(relyio.content('post').listAll())` makes it an array. `limit` is how many each
1827
+ * request reads; every other option is a list's, sent with each page.
1800
1828
  *
1801
- * // Access: product.title (default locale)
1802
- * // Access: product._metadata.localizations['es-ES'].title
1803
- * // Access: product._metadata.availableLocales
1804
- * ```
1829
+ * Iterating throws when a page says there is more and hands back no cursor to read it by, rather
1830
+ * than ending the walk short.
1805
1831
  */
1806
- allLocales() {
1807
- this.fetchAllLocales = true;
1808
- this.localeValue = void 0;
1809
- this.localesArray = void 0;
1810
- return this;
1832
+ listAll(options = {}) {
1833
+ return this.walk(options);
1811
1834
  }
1812
- /**
1813
- * Select specific fields to return (projection)
1814
- * @param fieldNames - Array of field names to include
1815
- *
1816
- * @example
1817
- * ```typescript
1818
- * .fields(['id', 'title', 'price', 'brand.name'])
1819
- * ```
1820
- */
1821
- fields(fieldNames) {
1822
- this.fieldsProjection = fieldNames;
1823
- return this;
1835
+ async *walk(options) {
1836
+ let cursor;
1837
+ for (; ; ) {
1838
+ const { data, page } = await this.list(
1839
+ cursor === void 0 ? options : { ...options, cursor }
1840
+ );
1841
+ yield* data;
1842
+ if (!page.hasMore) return;
1843
+ if (page.nextCursor === null) {
1844
+ throw new Error(
1845
+ `${this.typeName}: a page of the list says there is more and hands back no cursor to read it by`
1846
+ );
1847
+ }
1848
+ cursor = page.nextCursor;
1849
+ }
1824
1850
  }
1825
1851
  /**
1826
- * Include children in results (for hierarchical content)
1827
- * @param options - Children options or boolean to enable with defaults
1852
+ * One entry by its id or slug, or `null` when there is no such entry (REL-2415).
1853
+ *
1854
+ * `null` only for a missing **entry**: a type the schema does not declare, or a dataset with no
1855
+ * schema deployed, throws `ResourceNotFoundError` naming which (`context.resourceType`), so an
1856
+ * undeployed type never reads as a missing page.
1857
+ *
1858
+ * It takes the options of a list that are not about which entries or in what order — `include`,
1859
+ * `locale`, `dataset`, `fresh`, `children` (REL-2417). The route declares no `fields`, so there is
1860
+ * no `select`.
1828
1861
  *
1829
1862
  * @example
1830
1863
  * ```typescript
1831
- * // Include all children
1832
- * .children(true)
1833
- *
1834
- * // Include with options
1835
- * .children({ contentTypes: ['variant'], limit: 10 })
1864
+ * const post = await relyio.content('post').get('hello-world', { include: ['author'] })
1865
+ * if (post === null) return notFound()
1836
1866
  * ```
1837
1867
  */
1838
- children(options) {
1839
- if (typeof options === "boolean") {
1840
- this.childrenOptions = options ? {} : void 0;
1841
- } else {
1842
- this.childrenOptions = options;
1868
+ async get(idOrSlug, options = {}) {
1869
+ try {
1870
+ return await this.httpClient.fetchPayload("/v1/{contentType}/{id}", "get", {
1871
+ path: { contentType: this.typeName, id: idOrSlug },
1872
+ query: entryQuery(options, this.clientLocale()),
1873
+ headers: readHeaders(options)
1874
+ });
1875
+ } catch (error) {
1876
+ if (isMissingEntry(error)) return null;
1877
+ throw error;
1843
1878
  }
1844
- return this;
1845
- }
1846
- /**
1847
- * Set page number for page-based pagination
1848
- * @param pageNum - Page number (1-based)
1849
- */
1850
- page(pageNum) {
1851
- this.pageNumber = pageNum;
1852
- return this;
1853
1879
  }
1854
- /**
1855
- * Set page size for page-based pagination
1856
- * @param size - Items per page
1857
- */
1858
- pageSize(size) {
1859
- this.pageSizeValue = size;
1860
- return this;
1880
+ async update(target, data, options) {
1881
+ const { idempotencyKey, locale, ...placement } = options ?? {};
1882
+ const write = typeof target === "string" ? { id: target, locale: callLocale(locale, this.clientLocale()), ifMatch: void 0 } : versionCheckOf(target);
1883
+ return this.httpClient.fetchPayload(
1884
+ "/v1/{contentType}/{id}",
1885
+ "patch",
1886
+ {
1887
+ path: { contentType: this.typeName, id: write.id },
1888
+ body: {
1889
+ content: data,
1890
+ ...placement,
1891
+ ...write.locale === void 0 ? {} : { locale: write.locale }
1892
+ },
1893
+ headers: writeHeaders({ idempotencyKey, ifMatch: write.ifMatch })
1894
+ }
1895
+ );
1861
1896
  }
1862
- /**
1863
- * Execute the query and return all matching results
1864
- */
1865
- async execute() {
1866
- return readPayload(await this.executeWithMeta());
1897
+ async delete(target, options) {
1898
+ const write = typeof target === "string" ? { id: target, ifMatch: void 0 } : versionCheckOf(target);
1899
+ await this.httpClient.send("/v1/{contentType}/{id}", "delete", {
1900
+ path: { contentType: this.typeName, id: write.id },
1901
+ headers: writeHeaders({ idempotencyKey: options?.idempotencyKey, ifMatch: write.ifMatch })
1902
+ });
1867
1903
  }
1904
+ };
1905
+ __name(_EntryHandle, "EntryHandle");
1906
+ var EntryHandle = _EntryHandle;
1907
+
1908
+ // src/content/content-client.ts
1909
+ var _ContentClient = class _ContentClient extends EntryHandle {
1868
1910
  /**
1869
- * Execute the query and return the whole body — the items under `data`, the
1870
- * cursor facts under `page`.
1911
+ * Create a new content entry.
1912
+ *
1913
+ * `locale` defaults to the client's own, and `dataset` to the client's (`client.with({ dataset })`
1914
+ * sets it for every call).
1915
+ *
1916
+ * @example
1917
+ * ```typescript
1918
+ * const post = await relyio.content('post').create({ title: 'Hello' }, { slug: 'hello' })
1919
+ * ```
1871
1920
  */
1872
- async executeWithMeta() {
1873
- const response = await this.httpClient.send(
1921
+ async create(data, options = {}) {
1922
+ const { idempotencyKey, dataset, locale, ...body } = options;
1923
+ const sent = callLocale(locale, this.clientLocale());
1924
+ return this.httpClient.fetchPayload(
1874
1925
  "/v1/{contentType}",
1875
- "get",
1926
+ "post",
1876
1927
  {
1877
- path: { contentType: this.contentType },
1878
- query: this.buildQueryParams(),
1879
- headers: this.requestHeaders
1928
+ path: { contentType: this.typeName },
1929
+ body: { content: data, ...body, ...sent === void 0 ? {} : { locale: sent } },
1930
+ headers: { ...readHeaders({ dataset }), ...writeHeaders({ idempotencyKey }) }
1880
1931
  }
1881
1932
  );
1882
- return response.body;
1883
- }
1884
- /**
1885
- * Execute the query and return a single result (first match)
1886
- */
1887
- async first() {
1888
- const originalLimit = this.queryOptions.limit;
1889
- this.limit(1);
1890
- try {
1891
- const results = await this.execute();
1892
- return results.length > 0 ? results[0] ?? null : null;
1893
- } finally {
1894
- if (originalLimit !== void 0) {
1895
- this.queryOptions.limit = originalLimit;
1896
- } else {
1897
- delete this.queryOptions.limit;
1898
- }
1899
- }
1900
- }
1901
- /**
1902
- * Get paginated results
1903
- */
1904
- async paginate(limit = 20) {
1905
- return this.limit(limit).executeWithMeta();
1906
1933
  }
1907
1934
  /**
1908
- * Create an async iterator for streaming results
1935
+ * Get version history for one locale of an entry.
1936
+ *
1937
+ * Versions are numbered per locale, so this is one locale's timeline: `locale` defaults to the
1938
+ * client's own, else the schema default; no fallback is applied, and a locale
1939
+ * with no history returns an empty array rather than another locale's rows. Every row names the
1940
+ * locale it belongs to.
1909
1941
  */
1910
- async *[Symbol.asyncIterator]() {
1911
- const limit = this.queryOptions.limit ?? 100;
1912
- let cursor;
1913
- let hasMore = true;
1914
- while (hasMore) {
1915
- if (cursor != null && cursor !== "") {
1916
- this.cursor(cursor);
1917
- }
1918
- const result = await this.limit(limit).executeWithMeta();
1919
- for (const item of readPayload(result)) {
1920
- yield item;
1942
+ async versions(id, options) {
1943
+ const locale = callLocale(options?.locale, this.clientLocale());
1944
+ return this.httpClient.fetchPayload("/v1/{contentType}/{id}/versions", "get", {
1945
+ path: { contentType: this.typeName, id },
1946
+ query: {
1947
+ limit: options?.limit ?? 10,
1948
+ ...locale === void 0 ? {} : { locale }
1921
1949
  }
1922
- hasMore = result.page.hasMore;
1923
- cursor = result.page.nextCursor ?? void 0;
1924
- }
1925
- }
1926
- /**
1927
- * Execute the query and return all results as an array (for large datasets)
1928
- */
1929
- async all() {
1930
- const allItems = [];
1931
- for await (const item of this) {
1932
- allItems.push(item);
1933
- }
1934
- return allItems;
1950
+ });
1935
1951
  }
1936
1952
  /**
1937
- * Clone the query builder
1953
+ * One version of an entry: `version(id, { version, locale })`.
1954
+ *
1955
+ * `locale` defaults to the client's own, else the schema default. Version numbers are per locale,
1956
+ * so version 2 is different content in each one.
1957
+ *
1958
+ * There is no `null`: a version the locale does not have, an entry that is not there, a type the
1959
+ * schema does not declare and a dataset with no schema all throw `ResourceNotFoundError`, its
1960
+ * `context.resourceType` naming which (REL-2409 Q12).
1938
1961
  */
1939
- clone() {
1940
- const cloned = new _ContentQueryBuilder(
1941
- this.httpClient,
1942
- this.contentType,
1943
- this.clientLocale
1944
- );
1945
- cloned.queryOptions = JSON.parse(JSON.stringify(this.queryOptions));
1946
- cloned.localeValue = this.localeValue;
1947
- cloned.localesArray = this.localesArray ? [...this.localesArray] : void 0;
1948
- cloned.fetchAllLocales = this.fetchAllLocales;
1949
- cloned.fieldsProjection = this.fieldsProjection ? [...this.fieldsProjection] : void 0;
1950
- cloned.pageNumber = this.pageNumber;
1951
- cloned.pageSizeValue = this.pageSizeValue;
1952
- cloned.requestHeaders = this.requestHeaders ? { ...this.requestHeaders } : void 0;
1953
- cloned.freshValue = this.freshValue;
1954
- return cloned;
1962
+ async version(id, options) {
1963
+ const locale = callLocale(options.locale, this.clientLocale());
1964
+ return this.httpClient.fetchPayload("/v1/{contentType}/{id}/versions/{versionNumber}", "get", {
1965
+ path: {
1966
+ contentType: this.typeName,
1967
+ id,
1968
+ versionNumber: String(options.version)
1969
+ },
1970
+ query: locale === void 0 ? void 0 : { locale }
1971
+ });
1955
1972
  }
1956
- /**
1957
- * Get the current query options
1958
- */
1959
- getQueryOptions() {
1960
- return { ...this.queryOptions };
1973
+ async restore(target, options) {
1974
+ const { version, locale, idempotencyKey } = options;
1975
+ const write = typeof target === "string" ? { id: target, locale, ifMatch: void 0 } : versionCheckOf(target);
1976
+ return this.httpClient.fetchPayload("/v1/{contentType}/{id}/versions/{versionNumber}/restore", "post", {
1977
+ path: {
1978
+ contentType: this.typeName,
1979
+ id: write.id,
1980
+ versionNumber: String(version)
1981
+ },
1982
+ body: { locale: write.locale },
1983
+ headers: writeHeaders({ idempotencyKey, ifMatch: write.ifMatch })
1984
+ });
1961
1985
  }
1962
1986
  /**
1963
- * Execute the query and return the whole body.
1987
+ * The direct children of a hierarchical entry, each read as `get` reads an entry.
1964
1988
  *
1965
- * Identical to {@link executeWithMeta} now that the list serves one shape;
1966
- * kept as the name callers already import.
1967
- */
1968
- async executeWithMetaExtended() {
1969
- return this.executeWithMeta();
1970
- }
1971
- /**
1972
- * `count()` and `exists()` are gone, and their absence is the honest answer
1973
- * rather than an omission (`INV-RD-CAPABILITY-IS-HONEST`).
1989
+ * It takes `get`'s options — `include` (checked against the type), `locale` (a code, a list or
1990
+ * `'*'`), `dataset`, `fresh` and `children` — the terms its route declares as the entry route
1991
+ * does, sent the same way. The route lists one level and takes no depth (REL-2195); a tree nested
1992
+ * under each child is the `children` option.
1974
1993
  *
1975
- * `count()` sent `withCount` and read `pagination.total`; API2 moved the
1976
- * customer list onto the **cursor** dialect, whose page facts are
1977
- * `{ nextCursor, hasMore }` and nothing else, so no total reaches this
1978
- * surface at all. Keeping the method would have meant either a request that
1979
- * always throws or a number assembled from a page the caller happened to
1980
- * fetch — a count of what was returned, presented as a count of what matched
1981
- * (rule 6). The plan's §3.3 listed `count` among six methods a type would
1982
- * repair; what the type actually shows is that the capability is no longer
1983
- * there. A caller that wants "is there at least one" fetches one and looks at
1984
- * `data.length`; a caller that wants a true total uses `POST /v1/search`,
1985
- * which is the offset dialect precisely because a facet-bearing result wants
1986
- * one (Decision 5).
1987
- */
1988
- /**
1989
- * Build query parameters for the HTTP request
1994
+ * @param id - Parent entry ID
1990
1995
  */
1991
- buildQueryParams() {
1992
- const params = {};
1993
- if (this.queryOptions.where && Object.keys(this.queryOptions.where).length > 0) {
1994
- params.filters = JSON.stringify(this.queryOptions.where);
1995
- }
1996
- if (this.queryOptions.select) {
1997
- params.fields = this.queryOptions.select.join(",");
1998
- }
1999
- if (this.fieldsProjection) {
2000
- params.fields = this.fieldsProjection.join(",");
2001
- }
2002
- if (this.queryOptions.include) {
2003
- params.include = this.queryOptions.include.join(",");
2004
- }
2005
- if (this.queryOptions.sort) {
2006
- const sortArray = Object.entries(this.queryOptions.sort).map(
2007
- ([field, direction]) => `${field}:${direction}`
2008
- );
2009
- params.sort = sortArray.join(",");
2010
- }
2011
- if (this.queryOptions.limit !== void 0) {
2012
- params.limit = this.queryOptions.limit;
2013
- }
2014
- if (this.queryOptions.offset !== void 0) {
2015
- params.offset = this.queryOptions.offset;
2016
- }
2017
- if (this.queryOptions.cursor !== void 0) {
2018
- params.cursor = this.queryOptions.cursor;
2019
- }
2020
- const clientLocale = this.clientLocale();
2021
- if (this.localeValue != null && this.localeValue !== "") {
2022
- params.locale = this.localeValue;
2023
- } else if (this.fetchAllLocales) {
2024
- params.locale = "*";
2025
- } else if (this.localesArray && this.localesArray.length > 0) {
2026
- params.locales = this.localesArray.join(",");
2027
- } else if (clientLocale != null && clientLocale !== "") {
2028
- params.locale = clientLocale;
2029
- }
2030
- if (this.pageNumber !== void 0) {
2031
- params.page = this.pageNumber;
2032
- }
2033
- if (this.pageSizeValue !== void 0) {
2034
- params.pageSize = this.pageSizeValue;
2035
- }
2036
- if (this.childrenOptions) {
2037
- if (this.childrenOptions.contentTypes) {
2038
- params.children = this.childrenOptions.contentTypes.join(",");
2039
- } else {
2040
- params.children = "true";
2041
- }
2042
- if (this.childrenOptions.limit !== void 0) {
2043
- params.childrenLimit = this.childrenOptions.limit;
2044
- }
2045
- }
2046
- if (this.freshValue !== void 0) {
2047
- params[FRESH_PARAMETER] = this.freshValue;
2048
- }
2049
- return params;
2050
- }
2051
- };
2052
- __name(_ContentQueryBuilder, "ContentQueryBuilder");
2053
- var ContentQueryBuilder = _ContentQueryBuilder;
2054
- var _ContentByIdBuilder = class _ContentByIdBuilder {
2055
- /**
2056
- * @param locale - The client's locale (`withLocale()`, `updateConfig({ locale })`), sent when the
2057
- * read names none: the function `TypedRelyIOClient` hands over, or a locale fixed for it
2058
- */
2059
- constructor(httpClient, contentType, id, locale) {
2060
- this.httpClient = httpClient;
2061
- this.contentType = contentType;
2062
- this.id = id;
2063
- __publicField(this, "includeRelations");
2064
- __publicField(this, "localeValue");
2065
- __publicField(this, "localesArray");
2066
- __publicField(this, "fetchAllLocales", false);
2067
- __publicField(this, "childrenOptions");
2068
- __publicField(this, "requestHeaders");
2069
- /** The client's locale, asked when the read is sent rather than copied when it was built. */
2070
- __publicField(this, "clientLocale");
2071
- __publicField(this, "freshValue");
2072
- this.clientLocale = readClientLocale(locale);
2073
- }
2074
- /**
2075
- * The feed position this read follows — a change's `fresh` (`FeedChange`), or a resync's. Sent as
2076
- * `?_fresh`, the one term the API declares for it: no cache — the Content API's edge or the
2077
- * browser's own — holds an answer for that position from before it, so the read is answered after
2078
- * the change, and every read naming the same position shares that answer. The answer is the
2079
- * read's own; omit it for a read no change prompted.
2080
- */
2081
- fresh(position) {
2082
- this.freshValue = position;
2083
- return this;
2084
- }
2085
- /**
2086
- * Expand references into the entries they point at: one include path per element (REL-1801).
2087
- * A path names fields through lists, page builders, objects and references —
2088
- * `'sections.features'`, `'sections.customers.logo'` — and may pick the fields each entry returns,
2089
- * `'sections.features(name,slug)'`. Each is checked against the schema: a path that holds no
2090
- * reference does not compile, and the read's type follows the paths — an expanded reference is the
2091
- * entry, every other one its id. A second call adds to the first.
2092
- */
2093
- include(paths) {
2094
- this.includeRelations = [...this.includeRelations ?? [], ...paths];
2095
- return this;
2096
- }
2097
- /**
2098
- * Set locale for localized content
2099
- * @param locale - Locale code (e.g., 'en-US', 'de-DE', 'fr-FR')
2100
- */
2101
- locale(locale) {
2102
- this.localeValue = locale;
2103
- this.localesArray = void 0;
2104
- this.fetchAllLocales = false;
2105
- return this;
2106
- }
2107
- /**
2108
- * Fetch content in multiple specific locales
2109
- * Returns primary locale at top level, others in _metadata.localizations
2110
- *
2111
- * @param localeCodes - Array of locale codes to fetch
2112
- */
2113
- locales(localeCodes) {
2114
- this.localesArray = localeCodes;
2115
- this.localeValue = void 0;
2116
- this.fetchAllLocales = false;
2117
- return this;
2118
- }
2119
- /**
2120
- * Headers this one read adds to the client's own, e.g. `X-RelyIO-Dataset` to read another dataset
2121
- * the key can see. A name the client already sends is replaced for this request only; the client
2122
- * is not changed.
2123
- */
2124
- headers(headers) {
2125
- this.requestHeaders = headers;
2126
- return this;
2127
- }
2128
- /**
2129
- * Fetch content in all available locales
2130
- * Returns default locale at top level, others in _metadata.localizations
2131
- */
2132
- allLocales() {
2133
- this.fetchAllLocales = true;
2134
- this.localeValue = void 0;
2135
- this.localesArray = void 0;
2136
- return this;
2137
- }
2138
- /**
2139
- * Include children in results (for hierarchical content)
2140
- * @param options - Children options or boolean to enable with defaults
2141
- */
2142
- children(options) {
2143
- if (typeof options === "boolean") {
2144
- this.childrenOptions = options ? {} : void 0;
2145
- } else {
2146
- this.childrenOptions = options;
2147
- }
2148
- return this;
2149
- }
2150
- /**
2151
- * Execute the query and return the content item
2152
- */
2153
- async execute() {
2154
- const params = {};
2155
- if (this.includeRelations != null) {
2156
- params.include = this.includeRelations.join(",");
2157
- }
2158
- const clientLocale = this.clientLocale();
2159
- if (this.localeValue != null && this.localeValue !== "") {
2160
- params.locale = this.localeValue;
2161
- } else if (this.fetchAllLocales) {
2162
- params.locale = "*";
2163
- } else if (this.localesArray && this.localesArray.length > 0) {
2164
- params.locales = this.localesArray.join(",");
2165
- } else if (clientLocale != null && clientLocale !== "") {
2166
- params.locale = clientLocale;
2167
- }
2168
- if (this.childrenOptions != null) {
2169
- if (this.childrenOptions.contentTypes) {
2170
- params.children = this.childrenOptions.contentTypes.join(",");
2171
- } else {
2172
- params.children = "true";
2173
- }
2174
- if (this.childrenOptions.limit !== void 0) {
2175
- params.childrenLimit = this.childrenOptions.limit;
2176
- }
2177
- }
2178
- if (this.freshValue !== void 0) {
2179
- params[FRESH_PARAMETER] = this.freshValue;
2180
- }
2181
- try {
2182
- return await this.httpClient.fetchPayload(
2183
- "/v1/{contentType}/{id}",
2184
- "get",
2185
- {
2186
- path: { contentType: this.contentType, id: this.id },
2187
- query: params,
2188
- headers: this.requestHeaders
2189
- }
2190
- );
2191
- } catch (error) {
2192
- if (isResourceNotFound(error)) return null;
2193
- throw error;
2194
- }
2195
- }
2196
- };
2197
- __name(_ContentByIdBuilder, "ContentByIdBuilder");
2198
- var ContentByIdBuilder = _ContentByIdBuilder;
2199
-
2200
- // src/http/write-options.ts
2201
- function mintIdempotencyKey() {
2202
- const webCrypto = globalThis.crypto;
2203
- if (typeof webCrypto?.randomUUID !== "function") return void 0;
2204
- return webCrypto.randomUUID();
2205
- }
2206
- __name(mintIdempotencyKey, "mintIdempotencyKey");
2207
- function writeHeaders(options) {
2208
- const headers = {};
2209
- if (options !== void 0 && "ifMatch" in options && options.ifMatch !== void 0) {
2210
- headers[HEADER_IF_MATCH.name] = options.ifMatch;
2211
- }
2212
- const idempotencyKey = options?.idempotencyKey ?? mintIdempotencyKey();
2213
- if (idempotencyKey !== void 0) {
2214
- headers[HEADER_IDEMPOTENCY_KEY.name] = idempotencyKey;
2215
- }
2216
- return Object.keys(headers).length > 0 ? headers : void 0;
2217
- }
2218
- __name(writeHeaders, "writeHeaders");
2219
- function requestHeaders(headers, typed) {
2220
- if (headers === void 0) return typed;
2221
- if (typed === void 0) return headers;
2222
- const typedNames = new Set(Object.keys(typed).map((name) => name.toLowerCase()));
2223
- const own = Object.entries(headers).filter(([name]) => !typedNames.has(name.toLowerCase()));
2224
- return { ...Object.fromEntries(own), ...typed };
2225
- }
2226
- __name(requestHeaders, "requestHeaders");
2227
- function entityTagOf(response) {
2228
- return headerValue(response.headers, HEADER_ENTITY_TAG.name);
2229
- }
2230
- __name(entityTagOf, "entityTagOf");
2231
-
2232
- // src/content/content-client.ts
2233
- var _ContentClient = class _ContentClient {
2234
- /**
2235
- * @param locale - The client's locale: the function `TypedRelyIOClient` hands over, asked on
2236
- * every call, or a locale fixed for this client
2237
- */
2238
- constructor(httpClient, contentType, locale) {
2239
- this.httpClient = httpClient;
2240
- this.contentType = contentType;
2241
- /** The client's locale, asked on every call rather than copied when this client was taken. */
2242
- __publicField(this, "clientLocale");
2243
- this.clientLocale = readClientLocale(locale);
2244
- }
2245
- /**
2246
- * Create a new query builder for this content type
2247
- */
2248
- query() {
2249
- return new ContentQueryBuilder(this.httpClient, this.contentType, this.clientLocale);
2250
- }
2251
- /**
2252
- * Find content by ID with fluent options
2253
- */
2254
- findById(id) {
2255
- return new ContentByIdBuilder(this.httpClient, this.contentType, id, this.clientLocale);
2256
- }
2257
- /**
2258
- * Fetch all content items
2259
- * @param locale - Optional locale for localized content
2260
- */
2261
- async fetchAll(locale) {
2262
- const builder = this.query();
2263
- if (locale != null && locale !== "") {
2264
- builder.locale(locale);
2265
- }
2266
- return builder.execute();
2267
- }
2268
- async fetch(options) {
2269
- const query = this.query();
2270
- if (options?.where) {
2271
- query.where(options.where);
2272
- }
2273
- const builder = query;
2274
- if (options?.limit != null) {
2275
- builder.limit(options.limit);
2276
- }
2277
- if (options?.offset != null) {
2278
- builder.offset(options.offset);
2279
- }
2280
- if (options?.sort) {
2281
- builder.sort(options.sort);
2282
- }
2283
- if (options?.include) {
2284
- builder.include([...options.include]);
2285
- }
2286
- if (options?.locale != null && options.locale !== "") {
2287
- builder.locale(options.locale);
2288
- }
2289
- if (options?.fields != null) {
2290
- builder.fields(options.fields);
2291
- }
2292
- if (options?.children != null) {
2293
- builder.children(options.children);
2294
- }
2295
- return builder.execute();
2296
- }
2297
- async fetchOne(id, options) {
2298
- const builder = this.findById(id);
2299
- if (options?.include != null) {
2300
- builder.include([...options.include]);
2301
- }
2302
- if (options?.locale != null && options.locale !== "") {
2303
- builder.locale(options.locale);
2304
- }
2305
- if (options?.headers != null) {
2306
- builder.headers(options.headers);
2307
- }
2308
- return builder.execute();
2309
- }
2310
- async fetchOneWithTag(id, options) {
2311
- const query = {};
2312
- if (options?.include != null) {
2313
- query.include = options.include.join(",");
2314
- }
2315
- const locale = options?.locale ?? this.clientLocale();
2316
- if (locale != null && locale !== "") query.locale = locale;
2317
- const response = await this.httpClient.send(
2318
- "/v1/{contentType}/{id}",
2319
- "get",
2320
- { path: { contentType: this.contentType, id }, query, headers: options?.headers }
2321
- );
2322
- return { entry: readPayload(response.body), entityTag: entityTagOf(response) };
2323
- }
2324
- /**
2325
- * Fetch the first content item matching the conditions
2326
- * @param where - Filter conditions
2327
- * @param locale - Optional locale
2328
- */
2329
- async fetchFirst(where, locale) {
2330
- const builder = this.query();
2331
- if (where != null) {
2332
- builder.where(where);
2333
- }
2334
- if (locale != null && locale !== "") {
2335
- builder.locale(locale);
2336
- }
2337
- return builder.first();
2338
- }
2339
- /**
2340
- * Create a new content item
2341
- * @param data - Content data
2342
- * @param options - Create options (slug, locale, parentId, sortOrder), and `headers` this one
2343
- * request adds — e.g. `{ 'X-RelyIO-Dataset': 'staging' }` to write a dataset other than the
2344
- * client's. They are sent, never put in the body; `idempotencyKey` still wins over an
2345
- * `Idempotency-Key` among them. `locale` defaults to the client's own (`withLocale()`).
2346
- */
2347
- async create(data, options) {
2348
- const { idempotencyKey, headers, locale, ...body } = options ?? {};
2349
- const sent = callLocale(locale, this.clientLocale());
2350
- return this.httpClient.fetchPayload(
2351
- "/v1/{contentType}",
2352
- "post",
2353
- {
2354
- path: { contentType: this.contentType },
2355
- body: { content: data, ...body, ...sent === void 0 ? {} : { locale: sent } },
2356
- headers: requestHeaders(headers, writeHeaders({ idempotencyKey }))
2357
- }
2358
- );
2359
- }
2360
- /**
2361
- * Update a content item by ID (partial update)
2362
- * @param id - Entry ID
2363
- * @param data - Fields to update
2364
- * @param options - Update options (slug, locale, parentId, sortOrder), and `headers` this one
2365
- * request adds, as for {@link create}. `ifMatch` and `idempotencyKey` win over the same
2366
- * header among them. `locale` defaults to the client's own, the locale whose entity tag
2367
- * {@link fetchOneWithTag} reads, so the tag it hands back is checked against the same locale.
2368
- * @returns The entry, its `_metadata.brokenLinks` naming each entry link another entry publishes
2369
- * that this revision left pointing nowhere — empty when it took no heading away.
2370
- */
2371
- async update(id, data, options) {
2372
- const { idempotencyKey, ifMatch, headers, locale, ...body } = options ?? {};
2373
- const sent = callLocale(locale, this.clientLocale());
2374
- return this.httpClient.fetchPayload(
2375
- "/v1/{contentType}/{id}",
2376
- "patch",
2377
- {
2378
- path: { contentType: this.contentType, id },
2379
- body: { content: data, ...body, ...sent === void 0 ? {} : { locale: sent } },
2380
- headers: requestHeaders(headers, writeHeaders({ idempotencyKey, ifMatch }))
2381
- }
2382
- );
2383
- }
2384
- /**
2385
- * Delete a content item by ID
2386
- */
2387
- async delete(id, options) {
2388
- await this.httpClient.send("/v1/{contentType}/{id}", "delete", {
2389
- path: { contentType: this.contentType, id },
2390
- headers: writeHeaders(options)
2391
- });
2392
- }
2393
- /**
2394
- * Check if content exists by ID
2395
- */
2396
- async exists(id) {
2397
- try {
2398
- const item = await this.fetchOne(id);
2399
- return item !== null;
2400
- } catch (error) {
2401
- if (error instanceof ResourceNotFoundError) {
2402
- return false;
2403
- }
2404
- throw error;
2405
- }
2406
- }
2407
- /**
2408
- * Get version history for one locale of an entry.
2409
- *
2410
- * Versions are numbered per locale, so this is one locale's timeline: `locale` defaults to the
2411
- * client's own (`withLocale()`), else the schema default; no fallback is applied, and a locale
2412
- * with no history returns an empty array rather than another locale's rows. Every row names the
2413
- * locale it belongs to.
2414
- */
2415
- async versions(id, options) {
2416
- const locale = callLocale(options?.locale, this.clientLocale());
2417
- return this.httpClient.fetchPayload("/v1/{contentType}/{id}/versions", "get", {
2418
- path: { contentType: this.contentType, id },
2419
- query: {
2420
- limit: options?.limit ?? 10,
2421
- ...locale === void 0 ? {} : { locale }
2422
- }
2423
- });
2424
- }
2425
- /**
2426
- * Get a specific version of an entry.
2427
- *
2428
- * `locale` defaults to the client's own (`withLocale()`), else the schema default. Version
2429
- * numbers are per locale, so version 2 is different content in each one.
2430
- */
2431
- async version(id, versionNumber, options) {
2432
- const locale = callLocale(options?.locale, this.clientLocale());
2433
- return this.httpClient.fetchPayload("/v1/{contentType}/{id}/versions/{versionNumber}", "get", {
2434
- path: {
2435
- contentType: this.contentType,
2436
- id,
2437
- versionNumber: String(versionNumber)
2438
- },
2439
- query: locale === void 0 ? void 0 : { locale }
2440
- });
2441
- }
2442
- /**
2443
- * Revert one locale of an entry to a previous version.
2444
- *
2445
- * `locale` is **required**, and that is not a convention: versions are numbered per locale, so
2446
- * `versionNumber` alone is ambiguous, and defaulting it would silently revert the wrong
2447
- * translation. Only that locale's fields change — shared fields and every other locale are left
2448
- * untouched.
2449
- *
2450
- * The restore is validated as strictly as a publish, so it cannot publish invalid content, and it
2451
- * records a new version of its own rather than rewinding the counter — so the revert is auditable
2452
- * and itself reversible.
2453
- *
2454
- * @returns The entry, its `_metadata.leftOut` naming each value of the version the active schema
2455
- * no longer declares, and `_metadata.brokenLinks` the entry links it left pointing nowhere.
2456
- */
2457
- async restore(id, versionNumber, locale, options) {
2458
- return this.httpClient.fetchPayload("/v1/{contentType}/{id}/versions/{versionNumber}/restore", "post", {
2459
- path: {
2460
- contentType: this.contentType,
2461
- id,
2462
- versionNumber: String(versionNumber)
2463
- },
2464
- body: { locale },
2465
- headers: writeHeaders(options)
2466
- });
2467
- }
2468
- /**
2469
- * Get the direct children of a hierarchical entry. The route lists one level and takes no depth
2470
- * (REL-2195): `{ depth }` was sent as `?depth=`, which the API refused with `400`. A tree nested
2471
- * under each entry is a list or entry read's `query().children()`.
2472
- * @param id - Parent entry ID
2473
- * @param options - Include paths to expand on each child, and the locale to read
2474
- */
2475
- async children(id, options) {
2476
- const params = {};
2477
- if (options?.include != null) {
2478
- params.include = options.include.join(",");
2479
- }
2480
- const locale = options?.locale ?? this.clientLocale();
2481
- if (locale != null && locale !== "") {
2482
- params.locale = locale;
2483
- }
2484
- return this.httpClient.fetchPayload(
2485
- "/v1/{contentType}/{id}/children",
2486
- "get",
2487
- { path: { contentType: this.contentType, id }, query: params }
2488
- );
2489
- }
2490
- /**
2491
- * Get the content type name
2492
- */
2493
- getContentType() {
2494
- return this.contentType;
2495
- }
2496
- /**
2497
- * Create a new ContentClient for a different content type
2498
- */
2499
- ofType(contentType) {
2500
- return new _ContentClient(this.httpClient, contentType, this.clientLocale);
2501
- }
2502
- /**
2503
- * Create a new ContentClient with a different default locale, fixed: an update of the client's
2504
- * locale (`updateConfig({ locale })`) does not reach it
2505
- */
2506
- withLocale(locale) {
2507
- return new _ContentClient(this.httpClient, this.contentType, locale);
2508
- }
2509
- /**
2510
- * Get the default locale as it is now: the client's, or the one `withLocale()` fixed
2511
- */
2512
- getDefaultLocale() {
2513
- return this.clientLocale();
1996
+ async children(id, options = {}) {
1997
+ return this.httpClient.fetchPayload("/v1/{contentType}/{id}/children", "get", {
1998
+ path: { contentType: this.typeName, id },
1999
+ query: entryQuery(options, this.clientLocale()),
2000
+ headers: readHeaders(options)
2001
+ });
2514
2002
  }
2515
2003
  };
2516
2004
  __name(_ContentClient, "ContentClient");
@@ -2649,16 +2137,16 @@ var _AssetFileBuilder = class _AssetFileBuilder {
2649
2137
  get target() {
2650
2138
  return { contentType: this.assetType, id: this.entryUid };
2651
2139
  }
2652
- withLocale(locale) {
2140
+ localeOf(locale) {
2653
2141
  return locale ?? this.clientLocale();
2654
2142
  }
2655
2143
  /**
2656
2144
  * The `locale` body field of a multipart call: the session's, else the client's. The API finds
2657
- * the binding by it, so a session a `withLocale()` client opened must name the same locale on
2145
+ * the binding by it, so a session a `with({ locale })` client opened must name the same locale on
2658
2146
  * every later call or its `objectKey` is refused as another binding's.
2659
2147
  */
2660
2148
  localeField(locale) {
2661
- const sent = this.withLocale(locale);
2149
+ const sent = this.localeOf(locale);
2662
2150
  return hasText(sent) ? { locale: sent } : {};
2663
2151
  }
2664
2152
  /**
@@ -2666,7 +2154,7 @@ var _AssetFileBuilder = class _AssetFileBuilder {
2666
2154
  * Cloudflare Stream direct-creator URL (video). Resumable multipart is plan 17.
2667
2155
  */
2668
2156
  async presign(input) {
2669
- const locale = this.withLocale(input.locale);
2157
+ const locale = this.localeOf(input.locale);
2670
2158
  return this.httpClient.fetchPayload("/v1/{contentType}/{id}/file/presign", "post", {
2671
2159
  path: this.target,
2672
2160
  body: {
@@ -2683,7 +2171,7 @@ var _AssetFileBuilder = class _AssetFileBuilder {
2683
2171
  * R2; a no-op for Stream, whose readiness is webhook-driven).
2684
2172
  */
2685
2173
  async confirm(input = {}) {
2686
- const locale = this.withLocale(input.locale);
2174
+ const locale = this.localeOf(input.locale);
2687
2175
  return this.httpClient.fetchPayload("/v1/{contentType}/{id}/file/confirm", "post", {
2688
2176
  path: this.target,
2689
2177
  body: {
@@ -2698,7 +2186,7 @@ var _AssetFileBuilder = class _AssetFileBuilder {
2698
2186
  // ──────────────────────────────────────────────────────────────────────────
2699
2187
  /** Open an R2 multipart session for a large file (standalone of `presign`). */
2700
2188
  async createMultipart(input) {
2701
- const locale = this.withLocale(input.locale);
2189
+ const locale = this.localeOf(input.locale);
2702
2190
  return this.httpClient.fetchPayload("/v1/{contentType}/{id}/file/multipart/create", "post", {
2703
2191
  path: this.target,
2704
2192
  body: {
@@ -2763,7 +2251,7 @@ var _AssetFileBuilder = class _AssetFileBuilder {
2763
2251
  */
2764
2252
  async url(input = {}) {
2765
2253
  const params = {};
2766
- const locale = this.withLocale(input.locale);
2254
+ const locale = this.localeOf(input.locale);
2767
2255
  if (hasText(locale)) params.locale = locale;
2768
2256
  if (hasText(input.variantId)) params.variantId = input.variantId;
2769
2257
  if (input.purpose !== void 0) params.purpose = input.purpose;
@@ -2784,7 +2272,7 @@ var _AssetFileBuilder = class _AssetFileBuilder {
2784
2272
  */
2785
2273
  async status(options = {}) {
2786
2274
  const params = {};
2787
- const locale = this.withLocale(options.locale);
2275
+ const locale = this.localeOf(options.locale);
2788
2276
  if (hasText(locale)) params.locale = locale;
2789
2277
  return this.httpClient.fetchPayload(
2790
2278
  "/v1/{contentType}/{id}/file",
@@ -2797,7 +2285,7 @@ var _AssetFileBuilder = class _AssetFileBuilder {
2797
2285
  * a downgrade purges the gateway cache). Returns the refreshed asset entry.
2798
2286
  */
2799
2287
  async updateVisibility(visibility, options = {}) {
2800
- const locale = this.withLocale(options.locale);
2288
+ const locale = this.localeOf(options.locale);
2801
2289
  return this.httpClient.fetchPayload("/v1/{contentType}/{id}/file", "patch", {
2802
2290
  path: this.target,
2803
2291
  body: { visibility, ...hasText(locale) ? { locale } : {} }
@@ -2810,7 +2298,7 @@ var _AssetFileBuilder = class _AssetFileBuilder {
2810
2298
  * refreshed asset entry.
2811
2299
  */
2812
2300
  async setFocalPoint(focalPoint, options = {}) {
2813
- const locale = this.withLocale(options.locale);
2301
+ const locale = this.localeOf(options.locale);
2814
2302
  return this.httpClient.fetchPayload("/v1/{contentType}/{id}/file", "patch", {
2815
2303
  path: this.target,
2816
2304
  body: { focalPoint, ...hasText(locale) ? { locale } : {} }
@@ -2822,7 +2310,7 @@ var _AssetFileBuilder = class _AssetFileBuilder {
2822
2310
  */
2823
2311
  async delete(options = {}) {
2824
2312
  const params = {};
2825
- const locale = this.withLocale(options.locale);
2313
+ const locale = this.localeOf(options.locale);
2826
2314
  if (hasText(locale)) params.locale = locale;
2827
2315
  await this.httpClient.send("/v1/{contentType}/{id}/file", "delete", {
2828
2316
  path: this.target,
@@ -2848,22 +2336,27 @@ var _AssetFileBuilder = class _AssetFileBuilder {
2848
2336
  * @throws {ReadOnlyKeyError} When the key's lens does not show the asset and the key may not read
2849
2337
  * its binding either: nothing it can read would ever report the file.
2850
2338
  */
2339
+ /** The asset entry through the key's lens, or `null` where that lens finds none. */
2340
+ async readEntry(locale) {
2341
+ try {
2342
+ return await this.httpClient.fetchPayload("/v1/{contentType}/{id}", "get", {
2343
+ path: this.target,
2344
+ query: hasText(locale) ? { locale } : {}
2345
+ });
2346
+ } catch (error) {
2347
+ if (isResourceNotFound(error)) return null;
2348
+ throw error;
2349
+ }
2350
+ }
2851
2351
  async waitUntilReady(options = {}) {
2852
2352
  assertWaitOptions(options);
2853
2353
  const timeoutMs = options.timeoutMs ?? 6e4;
2854
2354
  const pollIntervalMs = options.pollIntervalMs ?? 1500;
2855
- const locale = this.withLocale(options.locale);
2355
+ const locale = this.localeOf(options.locale);
2856
2356
  const deadline = Date.now() + timeoutMs;
2857
2357
  const subject = `${this.assetType}/${this.entryUid}`;
2858
2358
  for (; ; ) {
2859
- const builder = new ContentByIdBuilder(
2860
- this.httpClient,
2861
- this.assetType,
2862
- this.entryUid,
2863
- locale
2864
- );
2865
- if (hasText(locale)) builder.locale(locale);
2866
- const entry = await builder.execute();
2359
+ const entry = await this.readEntry(locale);
2867
2360
  let status;
2868
2361
  if (entry === null) {
2869
2362
  const binding = await this.status({ ...hasText(locale) ? { locale } : {} });
@@ -3146,56 +2639,12 @@ var _AssetUploadBuilder = class _AssetUploadBuilder {
3146
2639
  };
3147
2640
  __name(_AssetUploadBuilder, "AssetUploadBuilder");
3148
2641
  var AssetUploadBuilder = _AssetUploadBuilder;
3149
- var _AssetClient = class _AssetClient {
3150
- /**
3151
- * @param locale - The client's locale: the function `TypedRelyIOClient` hands over, asked on
3152
- * every call, or a locale fixed for this client
3153
- */
3154
- constructor(httpClient, assetType, locale) {
3155
- this.httpClient = httpClient;
3156
- this.assetType = assetType;
3157
- /** The client's locale, asked on every call rather than copied when this client was taken. */
3158
- __publicField(this, "clientLocale");
3159
- this.clientLocale = readClientLocale(locale);
3160
- }
3161
- /**
3162
- * Create a query builder for this asset type. It resolves asset reads; its conditions name the
3163
- * asset type's fields, which the `file` and `urls` blocks are not.
3164
- */
3165
- query() {
3166
- return new ContentQueryBuilder(
3167
- this.httpClient,
3168
- this.assetType,
3169
- this.clientLocale
3170
- );
3171
- }
3172
- /** Find an asset entry by id (or slug) with fluent options. */
3173
- findById(id) {
3174
- return new ContentByIdBuilder(
3175
- this.httpClient,
3176
- this.assetType,
3177
- id,
3178
- this.clientLocale
3179
- );
3180
- }
3181
- async fetchOne(id, options) {
3182
- const builder = this.findById(id);
3183
- if (options?.include != null) builder.include([...options.include]);
3184
- if (options?.locale != null && options.locale !== "") builder.locale(options.locale);
3185
- return builder.execute();
3186
- }
3187
- /** Fetch the first asset entry matching the conditions, or `null`. */
3188
- async fetchFirst(where, locale) {
3189
- const builder = this.query();
3190
- if (where != null) builder.where(where);
3191
- if (locale != null && locale !== "") builder.locale(locale);
3192
- return builder.first();
3193
- }
2642
+ var _AssetClient = class _AssetClient extends EntryHandle {
3194
2643
  /**
3195
2644
  * Create a new asset entry as a draft. The binary is uploaded separately via
3196
2645
  * `file(uid)` (or use the high-level `upload()` helper), and the entry
3197
2646
  * auto-publishes when its file becomes ready (unless `publish: false`).
3198
- * `locale` defaults to the client's own (`withLocale()`).
2647
+ * `locale` defaults to the client's own.
3199
2648
  */
3200
2649
  async create(data, options) {
3201
2650
  const { idempotencyKey, locale, ...rest } = options ?? {};
@@ -3204,58 +2653,19 @@ var _AssetClient = class _AssetClient {
3204
2653
  "/v1/{contentType}",
3205
2654
  "post",
3206
2655
  {
3207
- path: { contentType: this.assetType },
2656
+ path: { contentType: this.typeName },
3208
2657
  body: { content: data, ...rest, ...sent === void 0 ? {} : { locale: sent } },
3209
2658
  headers: writeHeaders({ idempotencyKey })
3210
2659
  }
3211
2660
  );
3212
2661
  }
3213
- /**
3214
- * Update an asset entry's metadata fields (and/or slug). `locale` defaults to the client's own
3215
- * (`withLocale()`).
3216
- */
3217
- async update(id, data, options) {
3218
- const { idempotencyKey, ifMatch, locale, ...rest } = options ?? {};
3219
- const sent = callLocale(locale, this.clientLocale());
3220
- return this.httpClient.fetchPayload(
3221
- "/v1/{contentType}/{id}",
3222
- "patch",
3223
- {
3224
- path: { contentType: this.assetType, id },
3225
- body: { content: data, ...rest, ...sent === void 0 ? {} : { locale: sent } },
3226
- headers: writeHeaders({ idempotencyKey, ifMatch })
3227
- }
3228
- );
3229
- }
3230
- /** Delete an asset entry (cascades file-byte cleanup + gateway purge). */
3231
- async delete(id, options) {
3232
- await this.httpClient.send("/v1/{contentType}/{id}", "delete", {
3233
- path: { contentType: this.assetType, id },
3234
- headers: writeHeaders(options)
3235
- });
3236
- }
3237
2662
  /** Get the file sub-resource builder for a specific asset entry. */
3238
2663
  file(entryUid) {
3239
- return new AssetFileBuilder(this.httpClient, this.assetType, entryUid, this.clientLocale);
2664
+ return new AssetFileBuilder(this.httpClient, this.typeName, entryUid, this.clientLocale);
3240
2665
  }
3241
2666
  /** Start a high-level upload (create → presign → PUT → confirm). */
3242
2667
  upload(file) {
3243
- return new AssetUploadBuilder(this.httpClient, this.assetType, file, this.clientLocale);
3244
- }
3245
- /** Get the asset type id. */
3246
- getAssetType() {
3247
- return this.assetType;
3248
- }
3249
- /** Get the default locale as it is now: the client's, or the one `withLocale()` fixed. */
3250
- getDefaultLocale() {
3251
- return this.clientLocale();
3252
- }
3253
- /**
3254
- * Create a new AssetClient for this type with a different default locale, fixed: an update of the
3255
- * client's locale (`updateConfig({ locale })`) does not reach it.
3256
- */
3257
- withLocale(locale) {
3258
- return new _AssetClient(this.httpClient, this.assetType, locale);
2668
+ return new AssetUploadBuilder(this.httpClient, this.typeName, file, this.clientLocale);
3259
2669
  }
3260
2670
  };
3261
2671
  __name(_AssetClient, "AssetClient");
@@ -3267,1539 +2677,222 @@ function isAssetEntry(entry) {
3267
2677
  }
3268
2678
  __name(isAssetEntry, "isAssetEntry");
3269
2679
 
3270
- // src/content/singleton-client.ts
3271
- var _SingletonClient = class _SingletonClient {
2680
+ // src/content/document-handle.ts
2681
+ var _DocumentHandle = class _DocumentHandle {
3272
2682
  /**
2683
+ * @param name - The singleton key or settings group id the routes are addressed by
3273
2684
  * @param locale - The client's locale: the function `TypedRelyIOClient` hands over, asked on
3274
- * every call, or a locale fixed for this client
2685
+ * every call
3275
2686
  */
3276
- constructor(httpClient, singletonKey, locale) {
2687
+ constructor(httpClient, name, locale) {
3277
2688
  this.httpClient = httpClient;
3278
- this.singletonKey = singletonKey;
3279
- __publicField(this, "includeFields");
3280
- __publicField(this, "localeValue");
3281
- /** The client's locale, asked on every call rather than copied when this client was taken. */
2689
+ this.name = name;
2690
+ /** The client's locale, asked on every call rather than copied when this handle was taken. */
3282
2691
  __publicField(this, "clientLocale");
3283
2692
  this.clientLocale = readClientLocale(locale);
3284
2693
  }
3285
- /**
3286
- * Expand references into the entries they point at: one include path per element (REL-1801).
3287
- * A path names fields through lists, objects and references — `'hero.media.image'`,
3288
- * `'featured.author'` — and may pick the fields each entry returns, `'featured(title,slug)'`.
3289
- * Each is checked against the schema: a path that holds no reference does not compile, and the
3290
- * read's type follows the paths — an expanded reference is the entry, every other one its id. A
3291
- * second call adds to the first.
3292
- */
3293
- include(paths) {
3294
- this.includeFields = [...this.includeFields ?? [], ...paths];
3295
- return this;
3296
- }
3297
- /**
3298
- * Set locale for localized content
3299
- * @param locale - Locale code (e.g., 'en', 'de', 'fr')
3300
- */
3301
- locale(locale) {
3302
- this.localeValue = locale;
3303
- return this;
3304
- }
3305
- async fetch(options) {
3306
- try {
3307
- return (await this.fetchWithTag(options)).content;
3308
- } catch (error) {
3309
- if (isResourceNotFound(error)) return null;
3310
- throw error;
2694
+ async update(...args) {
2695
+ if (isCheckedUpdate(args)) {
2696
+ const [read, content2, options2] = args;
2697
+ const { locale, ifMatch } = versionCheckOf(read);
2698
+ return this.patch({ content: content2, locale, ifMatch }, options2);
3311
2699
  }
3312
- }
3313
- /**
3314
- * Fetch the singleton **and the entity tag that read served**, which is what
3315
- * `update`'s `ifMatch` takes (`INV-API-CONDITIONAL-WRITE-IS-REACHABLE`).
3316
- *
3317
- * `entityTag` is absent on a cache HIT: a cached body cannot know the current
3318
- * version, and a stale tag would refuse the very next write.
3319
- */
3320
- async fetchWithTag(options) {
3321
- const query = {};
3322
- const includeValue = this.includeFields === void 0 && options?.include === void 0 ? void 0 : [...this.includeFields ?? [], ...options?.include ?? []];
3323
- const localeValueFinal = options?.locale ?? this.localeValue ?? this.clientLocale();
3324
- if (includeValue) query.include = includeValue.join(",");
3325
- if (localeValueFinal != null && localeValueFinal !== "") query.locale = localeValueFinal;
3326
- if (options?.fresh !== void 0) query[FRESH_PARAMETER] = options.fresh;
3327
- const response = await this.httpClient.send(
3328
- "/v1/singletons/{key}",
3329
- "get",
3330
- { path: { key: this.singletonKey }, query }
3331
- );
3332
- return { content: readPayload(response.body), entityTag: entityTagOf(response) };
3333
- }
3334
- /**
3335
- * Update singleton content. The API auto-publishes on PATCH — there is no
3336
- * separate publish route; calling `update()` writes and publishes.
3337
- * @param data - The content fields to update
3338
- * @param locale - Optional locale for localized content (overrides the fluent locale, which
3339
- * overrides the client's own)
3340
- * @param options - `If-Match` / `Idempotency-Key`, both declared on this route
3341
- */
3342
- async update(data, locale, options) {
3343
- const finalLocale = this.localeOf(locale);
3344
- return this.httpClient.fetchPayload(
3345
- "/v1/singletons/{key}",
3346
- "patch",
3347
- {
3348
- path: { key: this.singletonKey },
3349
- body: {
3350
- content: data,
3351
- ...finalLocale != null && finalLocale !== "" && { locale: finalLocale }
3352
- },
3353
- headers: writeHeaders(options)
3354
- }
2700
+ const [content, options] = args;
2701
+ return this.patch(
2702
+ { content, locale: this.localeOf(options?.locale), ifMatch: void 0 },
2703
+ options
3355
2704
  );
3356
2705
  }
3357
2706
  /**
3358
- * Check if singleton exists
3359
- */
3360
- async exists() {
3361
- try {
3362
- const result = await this.fetch();
3363
- return result !== null;
3364
- } catch {
3365
- return false;
3366
- }
3367
- }
3368
- /**
3369
- * Get version history for one locale of the singleton.
2707
+ * Version history for one locale of the document.
3370
2708
  *
3371
2709
  * Versions are numbered per locale, so this is one locale's timeline: `locale` defaults to the
3372
- * fluent `.locale()`, then the client's own (`withLocale()`), then the schema default; no
3373
- * fallback is applied, and a locale with no history returns an empty array rather than another
3374
- * locale's rows. Every row names the locale it belongs to.
3375
- */
3376
- async versions(options) {
3377
- const locale = this.localeOf(options?.locale);
3378
- const payload = await this.httpClient.fetchPayload("/v1/singletons/{key}/versions", "get", {
3379
- path: { key: this.singletonKey },
3380
- query: {
3381
- limit: options?.limit ?? 10,
3382
- ...locale === void 0 ? {} : { locale }
3383
- }
2710
+ * client's own, then the schema default; no fallback is applied, and a locale with no history
2711
+ * returns an empty array rather than another locale's rows. Every row names the locale it belongs
2712
+ * to.
2713
+ */
2714
+ async versions(options = {}) {
2715
+ const locale = this.localeOf(options.locale);
2716
+ const payload = await this.readVersions({
2717
+ limit: options.limit ?? 10,
2718
+ ...locale === void 0 ? {} : { locale }
3384
2719
  });
3385
2720
  return payload.versions;
3386
2721
  }
3387
2722
  /**
3388
- * Get a specific version of the singleton.
2723
+ * One version of the document: `version({ version, locale })`. `locale` defaults to the client's
2724
+ * own, then the schema default; version numbers are per locale, so version 2 is different content
2725
+ * in each one.
3389
2726
  *
3390
- * `locale` defaults to the fluent `.locale()`, then the client's own (`withLocale()`), then the
3391
- * schema default. Version numbers are per locale, so version 2 is different content in each one.
2727
+ * There is no `null`: a version the locale does not have, a document nobody has written, one the
2728
+ * schema does not declare and a dataset with no schema all throw `ResourceNotFoundError`, its
2729
+ * `context.resourceType` naming which (REL-2409 Q12).
3392
2730
  */
3393
- async version(versionNumber, options) {
3394
- const locale = this.localeOf(options?.locale);
3395
- try {
3396
- return await this.httpClient.fetchPayload("/v1/singletons/{key}/versions/{versionNumber}", "get", {
3397
- path: { key: this.singletonKey, versionNumber: String(versionNumber) },
3398
- query: locale === void 0 ? void 0 : { locale }
3399
- });
3400
- } catch (error) {
3401
- if (isResourceNotFound(error)) return null;
3402
- throw error;
3403
- }
2731
+ async version(options) {
2732
+ const locale = this.localeOf(options.locale);
2733
+ return this.readVersion(String(options.version), locale === void 0 ? {} : { locale });
3404
2734
  }
3405
- /** The locale a write or history read sends: the call's, the fluent one, else the client's. */
2735
+ /** The locale a write or a history read sends: the call's, else the client's. */
3406
2736
  localeOf(named) {
3407
- return callLocale(named ?? this.localeValue, this.clientLocale());
3408
- }
3409
- /**
3410
- * Get the singleton key
3411
- */
3412
- getSingletonKey() {
3413
- return this.singletonKey;
3414
- }
3415
- /**
3416
- * Reset fluent options for reuse
3417
- */
3418
- reset() {
3419
- this.includeFields = void 0;
3420
- this.localeValue = void 0;
3421
- return this;
3422
- }
3423
- };
3424
- __name(_SingletonClient, "SingletonClient");
3425
- var SingletonClient = _SingletonClient;
3426
-
3427
- // src/content/settings-client.ts
3428
- var _SettingsClient = class _SettingsClient {
3429
- /**
3430
- * @param locale - The client's locale: the function `TypedRelyIOClient` hands over, asked on
3431
- * every call, or a locale fixed for this client
3432
- */
3433
- constructor(httpClient, groupId, locale) {
3434
- this.httpClient = httpClient;
3435
- this.groupId = groupId;
3436
- __publicField(this, "includeFields");
3437
- __publicField(this, "localeValue");
3438
- /** The client's locale, asked on every call rather than copied when this client was taken. */
3439
- __publicField(this, "clientLocale");
3440
- this.clientLocale = readClientLocale(locale);
3441
- }
3442
- /**
3443
- * Expand references into the entries they point at: one include path per element (REL-1801).
3444
- * A path names fields through lists, objects and references — `'hero.media.image'`,
3445
- * `'featured.author'` — and may pick the fields each entry returns, `'featured(title,slug)'`.
3446
- * Each is checked against the schema: a path that holds no reference does not compile, and the
3447
- * read's type follows the paths — an expanded reference is the entry, every other one its id. A
3448
- * second call adds to the first.
3449
- */
3450
- include(paths) {
3451
- this.includeFields = [...this.includeFields ?? [], ...paths];
3452
- return this;
3453
- }
3454
- /**
3455
- * Set locale for localized settings
3456
- * @param locale - Locale code (e.g., 'en', 'de', 'fr')
3457
- */
3458
- locale(locale) {
3459
- this.localeValue = locale;
3460
- return this;
3461
- }
3462
- async fetch(options) {
3463
- try {
3464
- return (await this.fetchWithTag(options)).settings;
3465
- } catch (error) {
3466
- if (isResourceNotFound(error)) return null;
3467
- throw error;
3468
- }
2737
+ return callLocale(named, this.clientLocale());
3469
2738
  }
3470
- /**
3471
- * Update settings group
3472
- *
3473
- * The fields travel as `content` beside an optional `locale`, the body the route validates
3474
- * (`UpdateSettingsDto`); any other top-level key is refused `REQUEST_INVALID`.
3475
- *
3476
- * @param settings - The settings fields to update
3477
- * @param locale - Optional locale for localized settings (overrides the fluent locale, which
3478
- * overrides the client's own)
3479
- */
3480
- async update(settings, locale, options) {
3481
- const finalLocale = this.localeOf(locale);
3482
- const body = {
3483
- content: settings,
3484
- ...finalLocale != null && finalLocale !== "" ? { locale: finalLocale } : {}
2739
+ /** The write's body: its data, and the locale it names when it names one. */
2740
+ static bodyOf(write) {
2741
+ return {
2742
+ content: write.content,
2743
+ ...write.locale === void 0 ? {} : { locale: write.locale }
3485
2744
  };
3486
- return this.httpClient.fetchPayload(
3487
- "/v1/settings/{groupId}",
3488
- "patch",
3489
- { path: { groupId: this.groupId }, body, headers: writeHeaders(options) }
3490
- );
3491
- }
3492
- /**
3493
- * Fetch the group **and the entity tag that read served**, which is what
3494
- * `update`'s and `reset`'s `ifMatch` take
3495
- * (`INV-API-CONDITIONAL-WRITE-IS-REACHABLE`).
3496
- */
3497
- async fetchWithTag(options) {
3498
- const query = {};
3499
- const includeValue = this.includeFields === void 0 && options?.include === void 0 ? void 0 : [...this.includeFields ?? [], ...options?.include ?? []];
3500
- const localeValueFinal = options?.locale ?? this.localeValue ?? this.clientLocale();
3501
- if (includeValue) query.include = includeValue.join(",");
3502
- if (localeValueFinal != null && localeValueFinal !== "") query.locale = localeValueFinal;
3503
- const response = await this.httpClient.send(
3504
- "/v1/settings/{groupId}",
3505
- "get",
3506
- { path: { groupId: this.groupId }, query }
3507
- );
3508
- return { settings: readPayload(response.body), entityTag: entityTagOf(response) };
3509
- }
3510
- /**
3511
- * Reset settings to default values
3512
- *
3513
- * The route reads its locale from `?locale=` and takes no body, so that is where it is sent.
3514
- *
3515
- * @param locale - Optional locale for localized settings (overrides the fluent locale, which
3516
- * overrides the client's own)
3517
- */
3518
- async reset(locale, options) {
3519
- const finalLocale = this.localeOf(locale);
3520
- return this.httpClient.fetchPayload(
3521
- "/v1/settings/{groupId}/reset",
3522
- "post",
3523
- {
3524
- path: { groupId: this.groupId },
3525
- query: finalLocale != null && finalLocale !== "" ? { locale: finalLocale } : void 0,
3526
- headers: writeHeaders(options)
3527
- }
3528
- );
3529
- }
3530
- /**
3531
- * Get default settings for this group
3532
- */
3533
- async getDefaults() {
3534
- return this.httpClient.fetchPayload(
3535
- "/v1/settings/{groupId}/defaults",
3536
- "get",
3537
- { path: { groupId: this.groupId } }
3538
- );
3539
- }
3540
- /**
3541
- * Check if settings group exists
3542
- */
3543
- async exists() {
3544
- try {
3545
- const result = await this.fetch();
3546
- return result !== null;
3547
- } catch (error) {
3548
- if (isResourceNotFound(error)) return false;
3549
- throw error;
3550
- }
3551
- }
3552
- /**
3553
- * Get version history for one locale of the settings group.
3554
- *
3555
- * Versions are numbered per locale, so this is one locale's timeline: `locale` defaults to the
3556
- * fluent `.locale()`, then the client's own (`withLocale()`), then the schema default; no
3557
- * fallback is applied, and a locale with no history returns an empty array rather than another
3558
- * locale's rows. Every row names the locale it belongs to.
3559
- */
3560
- async versions(options) {
3561
- const locale = this.localeOf(options?.locale);
3562
- const payload = await this.httpClient.fetchPayload("/v1/settings/{groupId}/versions", "get", {
3563
- path: { groupId: this.groupId },
3564
- query: {
3565
- limit: options?.limit ?? 10,
3566
- ...locale === void 0 ? {} : { locale }
3567
- }
3568
- });
3569
- return payload.versions;
3570
- }
3571
- /**
3572
- * Get a specific version of the settings.
3573
- *
3574
- * `locale` defaults to the fluent `.locale()`, then the client's own (`withLocale()`), then the
3575
- * schema default. Version numbers are per locale, so version 2 is different content in each one.
3576
- */
3577
- async version(versionNumber, options) {
3578
- const locale = this.localeOf(options?.locale);
3579
- try {
3580
- return await this.httpClient.fetchPayload("/v1/settings/{groupId}/versions/{versionNumber}", "get", {
3581
- path: { groupId: this.groupId, versionNumber: String(versionNumber) },
3582
- query: locale === void 0 ? void 0 : { locale }
3583
- });
3584
- } catch (error) {
3585
- if (isResourceNotFound(error)) return null;
3586
- throw error;
3587
- }
3588
- }
3589
- /** The locale a write or history read sends: the call's, the fluent one, else the client's. */
3590
- localeOf(named) {
3591
- return callLocale(named ?? this.localeValue, this.clientLocale());
3592
- }
3593
- /**
3594
- * Get the settings group ID
3595
- */
3596
- getGroupId() {
3597
- return this.groupId;
3598
2745
  }
3599
- /**
3600
- * Reset fluent options for reuse
3601
- */
3602
- resetOptions() {
3603
- this.includeFields = void 0;
3604
- this.localeValue = void 0;
3605
- return this;
2746
+ /** The two headers a write sends: its check, when it has one, and its `Idempotency-Key`. */
2747
+ static headersOf(write, options) {
2748
+ return writeHeaders({ idempotencyKey: options?.idempotencyKey, ifMatch: write.ifMatch });
3606
2749
  }
3607
2750
  };
3608
- __name(_SettingsClient, "SettingsClient");
3609
- var SettingsClient = _SettingsClient;
2751
+ __name(_DocumentHandle, "DocumentHandle");
2752
+ var DocumentHandle = _DocumentHandle;
2753
+ function isCheckedUpdate(args) {
2754
+ return isRead(args[0]);
2755
+ }
2756
+ __name(isCheckedUpdate, "isCheckedUpdate");
2757
+ function isRead(value) {
2758
+ return "_metadata" in value && typeof value._metadata === "object" && value._metadata !== null;
2759
+ }
2760
+ __name(isRead, "isRead");
3610
2761
 
3611
- // src/search/search-client.ts
3612
- var _SearchBuilder = class _SearchBuilder {
3613
- /**
3614
- * @param locale - The client's locale (`withLocale()`, `updateConfig({ locale })`), sent when
3615
- * `.locale()` names none: the function `TypedRelyIOClient` hands over, or a locale fixed for it
3616
- */
3617
- constructor(httpClient, query, locale) {
3618
- this.httpClient = httpClient;
3619
- __publicField(this, "query");
3620
- __publicField(this, "searchOptions", {});
3621
- __publicField(this, "requestHeaders");
3622
- __publicField(this, "freshValue");
3623
- /** The client's locale, asked when the query is sent rather than copied when it was built. */
3624
- __publicField(this, "clientLocale");
3625
- this.query = query;
3626
- this.clientLocale = readClientLocale(locale);
3627
- }
3628
- /**
3629
- * Headers this search adds to the client's own, e.g. `X-RelyIO-Dataset` to search another
3630
- * dataset the key can see. A name the client already sends is replaced for this search only.
3631
- */
3632
- headers(headers) {
3633
- this.requestHeaders = headers;
3634
- return this;
3635
- }
3636
- /**
3637
- * The feed position this search follows — a change's `fresh` (`FeedChange`), or a resync's. Sent
3638
- * as `?_fresh` on the query string beside the body: the edge keys a search on both, so no answer
3639
- * it holds from before that position can answer it. The answer is the search's own.
3640
- */
3641
- fresh(position) {
3642
- this.freshValue = position;
3643
- return this;
3644
- }
3645
- /**
3646
- * Search within a single content type. Where a schema types the client, the name is one of its
3647
- * content types and the results are that type's entries.
3648
- * @param contentType - Content type to search in
3649
- */
3650
- in(contentType) {
3651
- this.searchOptions.contentType = contentType;
3652
- this.searchOptions.contentTypes = void 0;
3653
- return this;
3654
- }
3655
- /**
3656
- * Search across multiple content types. Where a schema types the client, the names are its content
3657
- * types and the results are a union of their entries — every type's, for `'*'`.
3658
- * @param contentTypes - Array of content types or '*' for all
3659
- */
3660
- across(contentTypes) {
3661
- this.searchOptions.contentTypes = contentTypes;
3662
- this.searchOptions.contentType = void 0;
3663
- return this;
3664
- }
3665
- /**
3666
- * Add filter conditions, on the fields the content types declare. A condition on `_metadata` is
3667
- * refused here, before anything is sent.
3668
- * @param conditions - Filter conditions
3669
- */
3670
- where(conditions) {
3671
- refuseMetadataConditions(conditions);
3672
- this.searchOptions.where = {
3673
- ...this.searchOptions.where,
3674
- ...conditions
3675
- };
3676
- return this;
3677
- }
3678
- /**
3679
- * Specify which fields to search in
3680
- * @param fields - Field names to search
3681
- */
3682
- searchFields(fields) {
3683
- this.searchOptions.searchFields = fields;
3684
- return this;
3685
- }
3686
- /**
3687
- * Project specific fields in results
3688
- * @param projection - Field mapping
3689
- */
3690
- fields(projection) {
3691
- this.searchOptions.fields = projection;
3692
- return this;
3693
- }
3694
- /**
3695
- * Order the results by a field instead of by relevance. Called again, it adds a tie-breaker.
3696
- * `_score` names relevance itself.
3697
- * @param field - Field path to sort by (e.g. `updatedAt`, `pricing.basePrice`)
3698
- * @param direction - `asc` or `desc` (the route's default is `desc`)
3699
- */
3700
- sort(field, direction = "desc") {
3701
- this.searchOptions.sort = [...this.searchOptions.sort ?? [], { field, direction }];
3702
- return this;
3703
- }
3704
- /**
3705
- * Limit number of results
3706
- * @param count - Maximum results
3707
- */
3708
- limit(count) {
3709
- this.searchOptions.limit = count;
3710
- return this;
3711
- }
3712
- /**
3713
- * Skip results for pagination
3714
- * @param count - Number to skip
3715
- */
3716
- offset(count) {
3717
- this.searchOptions.offset = count;
3718
- return this;
3719
- }
3720
- /**
3721
- * Include relevance scores in results
3722
- */
3723
- includeScore() {
3724
- this.searchOptions.includeScore = true;
3725
- return this;
3726
- }
3727
- /**
3728
- * Include score breakdown explanation
3729
- */
3730
- explain() {
3731
- this.searchOptions.explain = true;
3732
- return this;
3733
- }
3734
- /**
3735
- * Set locale for localized content
3736
- * @param locale - Locale code
3737
- */
3738
- locale(locale) {
3739
- this.searchOptions.locale = locale;
3740
- return this;
3741
- }
3742
- /**
3743
- * Execute the search query
3744
- */
3745
- async execute() {
3746
- const body = {
3747
- q: this.query
3748
- };
3749
- if (this.searchOptions.contentType != null && this.searchOptions.contentType !== "") {
3750
- body.contentType = this.searchOptions.contentType;
3751
- }
3752
- if (this.searchOptions.contentTypes != null) {
3753
- body.contentTypes = this.searchOptions.contentTypes;
3754
- }
3755
- if (this.searchOptions.where && Object.keys(this.searchOptions.where).length > 0) {
3756
- body.where = this.searchOptions.where;
3757
- }
3758
- if (this.searchOptions.searchFields) {
3759
- body.searchFields = this.searchOptions.searchFields;
3760
- }
3761
- if (this.searchOptions.fields) {
3762
- body.fields = this.searchOptions.fields;
3763
- }
3764
- if (this.searchOptions.sort) {
3765
- body.sort = this.searchOptions.sort;
3766
- }
3767
- if (this.searchOptions.limit !== void 0) {
3768
- body.limit = this.searchOptions.limit;
3769
- }
3770
- if (this.searchOptions.offset !== void 0) {
3771
- body.offset = this.searchOptions.offset;
3772
- }
3773
- if (this.searchOptions.includeScore === true) {
3774
- body.includeScore = true;
3775
- }
3776
- if (this.searchOptions.explain === true) {
3777
- body.explain = true;
3778
- }
3779
- const locale = this.searchOptions.locale ?? this.clientLocale();
3780
- if (locale != null && locale !== "") {
3781
- body.locale = locale;
3782
- }
3783
- const response = await this.httpClient.send(
3784
- "/v1/search",
3785
- "post",
3786
- {
3787
- body,
3788
- headers: this.requestHeaders,
3789
- ...this.freshValue === void 0 ? {} : { query: { [FRESH_PARAMETER]: this.freshValue } }
3790
- }
3791
- );
3792
- return response.body;
3793
- }
3794
- /**
3795
- * Execute and return just the items (convenience method)
3796
- */
3797
- async items() {
3798
- return readPayload(await this.execute());
3799
- }
3800
- /**
3801
- * Get the first result
3802
- */
3803
- async first() {
3804
- return readPayload(await this.limit(1).execute())[0] ?? null;
3805
- }
3806
- };
3807
- __name(_SearchBuilder, "SearchBuilder");
3808
- var SearchBuilder = _SearchBuilder;
3809
- var _AutocompleteBuilder = class _AutocompleteBuilder {
3810
- /**
3811
- * @param locale - The client's locale (`withLocale()`, `updateConfig({ locale })`), sent when
3812
- * `.locale()` names none: the function `TypedRelyIOClient` hands over, or a locale fixed for it
3813
- */
3814
- constructor(httpClient, query, locale) {
3815
- this.httpClient = httpClient;
3816
- __publicField(this, "query");
3817
- __publicField(this, "autocompleteOptions", {});
3818
- /** The client's locale, asked when the query is sent rather than copied when it was built. */
3819
- __publicField(this, "clientLocale");
3820
- this.query = query;
3821
- this.clientLocale = readClientLocale(locale);
3822
- }
3823
- /**
3824
- * Autocomplete within a content type
3825
- * @param contentType - Content type to search
3826
- */
3827
- in(contentType) {
3828
- this.autocompleteOptions.contentType = contentType;
3829
- return this;
3830
- }
3831
- /**
3832
- * Field to autocomplete on
3833
- * @param fieldName - Field name (supports nested like 'category.name')
3834
- */
3835
- field(fieldName) {
3836
- this.autocompleteOptions.field = fieldName;
3837
- return this;
3838
- }
3839
- /**
3840
- * Enable fuzzy matching for typo tolerance
3841
- */
3842
- fuzzy() {
3843
- this.autocompleteOptions.fuzzy = true;
3844
- return this;
3845
- }
3846
- /**
3847
- * Add filter conditions, on the fields the content types declare. A condition on `_metadata` is
3848
- * refused here, before anything is sent.
3849
- * @param conditions - Filter conditions
3850
- */
3851
- where(conditions) {
3852
- refuseMetadataConditions(conditions);
3853
- this.autocompleteOptions.where = {
3854
- ...this.autocompleteOptions.where,
3855
- ...conditions
3856
- };
3857
- return this;
3858
- }
3859
- /**
3860
- * Limit number of suggestions
3861
- * @param count - Maximum suggestions
3862
- */
3863
- limit(count) {
3864
- this.autocompleteOptions.limit = count;
3865
- return this;
3866
- }
3867
- /**
3868
- * Set locale for localized content
3869
- * @param locale - Locale code
3870
- */
3871
- locale(locale) {
3872
- this.autocompleteOptions.locale = locale;
3873
- return this;
3874
- }
3875
- /**
3876
- * Execute the autocomplete query
3877
- */
3878
- async execute() {
3879
- if (this.autocompleteOptions.contentType == null || this.autocompleteOptions.contentType === "") {
3880
- throw new Error('Content type is required for autocomplete. Use .in("contentType")');
3881
- }
3882
- if (this.autocompleteOptions.field == null || this.autocompleteOptions.field === "") {
3883
- throw new Error('Field is required for autocomplete. Use .field("fieldName")');
3884
- }
3885
- const body = {
3886
- q: this.query,
3887
- contentType: this.autocompleteOptions.contentType,
3888
- field: this.autocompleteOptions.field
3889
- };
3890
- if (this.autocompleteOptions.fuzzy === true) {
3891
- body.fuzzy = true;
3892
- }
3893
- if (this.autocompleteOptions.where && Object.keys(this.autocompleteOptions.where).length > 0) {
3894
- body.where = this.autocompleteOptions.where;
3895
- }
3896
- if (this.autocompleteOptions.limit !== void 0) {
3897
- body.limit = this.autocompleteOptions.limit;
3898
- }
3899
- const locale = this.autocompleteOptions.locale ?? this.clientLocale();
3900
- if (locale != null && locale !== "") {
3901
- body.locale = locale;
3902
- }
3903
- const response = await this.httpClient.send("/v1/search/autocomplete", "post", { body });
3904
- return response.body;
3905
- }
3906
- /**
3907
- * Get just the suggestion texts
3908
- */
3909
- async texts() {
3910
- return readPayload(await this.execute()).map((suggestion) => suggestion.text);
3911
- }
3912
- };
3913
- __name(_AutocompleteBuilder, "AutocompleteBuilder");
3914
- var AutocompleteBuilder = _AutocompleteBuilder;
3915
- var _SimilarBuilder = class _SimilarBuilder {
3916
- /**
3917
- * @param locale - The client's locale (`withLocale()`, `updateConfig({ locale })`), sent when
3918
- * `.locale()` names none: the function `TypedRelyIOClient` hands over, or a locale fixed for it
3919
- */
3920
- constructor(httpClient, sourceId, locale) {
3921
- this.httpClient = httpClient;
3922
- __publicField(this, "sourceId");
3923
- __publicField(this, "similarOptions", {});
3924
- __publicField(this, "requestHeaders");
3925
- /** The client's locale, asked when the read is sent rather than copied when it was built. */
3926
- __publicField(this, "clientLocale");
3927
- this.sourceId = sourceId;
3928
- this.clientLocale = readClientLocale(locale);
3929
- }
3930
- /**
3931
- * Headers this read adds to the client's own, e.g. `X-RelyIO-Dataset` to read another dataset
3932
- * the key can see. A name the client already sends is replaced for this read only.
3933
- */
3934
- headers(headers) {
3935
- this.requestHeaders = headers;
3936
- return this;
3937
- }
3938
- /**
3939
- * Find similar content within a content type
3940
- * @param contentType - Content type to search
3941
- */
3942
- in(contentType) {
3943
- this.similarOptions.contentType = contentType;
3944
- this.similarOptions.contentTypes = void 0;
3945
- return this;
3946
- }
3947
- /**
3948
- * Find similar content across several content types, or `'*'` for every type
3949
- * @param contentTypes - Array of content types or '*' for all
3950
- */
3951
- across(contentTypes) {
3952
- this.similarOptions.contentTypes = contentTypes;
3953
- this.similarOptions.contentType = void 0;
3954
- return this;
3955
- }
3956
- /*
3957
- * No `where()` (REL-1452). Similar ranks from the source entry's stored vector, and the vector
3958
- * index can pre-filter by content type and locale only — never by an entry field. The API used to
3959
- * accept `where` here and apply nothing; it now refuses the property, so the builder does not
3960
- * offer a filter that cannot be honoured. Narrow with `.in()`, `.across()` and `.locale()`.
3961
- */
3962
- /**
3963
- * Limit number of results
3964
- * @param count - Maximum results
3965
- */
3966
- limit(count) {
3967
- this.similarOptions.limit = count;
3968
- return this;
3969
- }
3970
- /**
3971
- * Set locale for localized content
3972
- * @param locale - Locale code
3973
- */
3974
- locale(locale) {
3975
- this.similarOptions.locale = locale;
3976
- return this;
3977
- }
3978
- /**
3979
- * Execute the similar query
3980
- */
3981
- async execute() {
3982
- const { contentType, contentTypes } = this.similarOptions;
3983
- if ((contentType == null || contentType === "") && contentTypes == null) {
3984
- throw new Error(
3985
- 'Content type is required for similar search. Use .in("contentType") or .across("*")'
3986
- );
3987
- }
3988
- const body = { source: this.sourceId };
3989
- if (contentType != null && contentType !== "") {
3990
- body.contentType = contentType;
3991
- }
3992
- if (contentTypes != null) {
3993
- body.contentTypes = contentTypes;
3994
- }
3995
- if (this.similarOptions.limit !== void 0) {
3996
- body.limit = this.similarOptions.limit;
3997
- }
3998
- const locale = this.similarOptions.locale ?? this.clientLocale();
3999
- if (locale != null && locale !== "") {
4000
- body.locale = locale;
4001
- }
4002
- const response = await this.httpClient.send(
4003
- "/v1/search/similar",
4004
- "post",
4005
- { body, headers: this.requestHeaders }
4006
- );
4007
- return response.body;
4008
- }
4009
- /**
4010
- * Get just the similar items
4011
- */
4012
- async items() {
4013
- return readPayload(await this.execute());
4014
- }
4015
- /**
4016
- * Get the most similar item
4017
- */
4018
- async first() {
4019
- return readPayload(await this.limit(1).execute())[0] ?? null;
4020
- }
4021
- };
4022
- __name(_SimilarBuilder, "SimilarBuilder");
4023
- var SimilarBuilder = _SimilarBuilder;
4024
- var _SearchClient = class _SearchClient {
4025
- /**
4026
- * @param locale - The client's locale (`withLocale()`, `updateConfig({ locale })`): every builder
4027
- * here sends it when the query names none, as `content()`'s reads do — the function
4028
- * `TypedRelyIOClient` hands over, or a locale fixed for this client
4029
- */
4030
- constructor(httpClient, locale) {
4031
- this.httpClient = httpClient;
4032
- /** The client's locale, handed to every builder here to ask when it sends. */
4033
- __publicField(this, "clientLocale");
4034
- this.clientLocale = readClientLocale(locale);
4035
- }
4036
- /**
4037
- * Start a search query
4038
- * @param query - Search query string
4039
- */
4040
- search(query) {
4041
- return new SearchBuilder(this.httpClient, query, this.clientLocale);
4042
- }
4043
- /**
4044
- * Start an autocomplete query
4045
- * @param query - Partial query to autocomplete
4046
- */
4047
- autocomplete(query) {
4048
- return new AutocompleteBuilder(this.httpClient, query, this.clientLocale);
4049
- }
4050
- /**
4051
- * Find content similar to a given entry
4052
- * @param sourceId - Entry ID to find similar content for
4053
- */
4054
- similar(sourceId) {
4055
- return new SimilarBuilder(this.httpClient, sourceId, this.clientLocale);
4056
- }
4057
- };
4058
- __name(_SearchClient, "SearchClient");
4059
- var SearchClient = _SearchClient;
4060
-
4061
- // src/batch/batch-client.ts
4062
- var _BatchBuilder = class _BatchBuilder {
4063
- /**
4064
- * @param locale - The client's locale (`withLocale()`, `updateConfig({ locale })`), each
4065
- * operation's when it names none: the function `TypedRelyIOClient` hands over, or a locale fixed
4066
- * for this batch
4067
- */
4068
- constructor(httpClient, locale) {
4069
- this.httpClient = httpClient;
4070
- __publicField(this, "operations", []);
4071
- __publicField(this, "defaultOptions", {
4072
- atomic: false,
4073
- dryRun: false,
4074
- returnData: true,
4075
- continueOnError: true
4076
- });
4077
- __publicField(this, "requestHeaders");
4078
- /** The client's locale, asked as each operation is added rather than copied when this was built. */
4079
- __publicField(this, "clientLocale");
4080
- this.clientLocale = readClientLocale(locale);
4081
- }
4082
- /**
4083
- * An operation's body with its locale: the one it names, else the client's as it is when the
4084
- * operation is added.
4085
- */
4086
- localized(data) {
4087
- const locale = callLocale(data.locale, this.clientLocale());
4088
- return locale === void 0 ? data : { ...data, locale };
4089
- }
4090
- // ==========================================================================
4091
- // ENTRY OPERATIONS
4092
- // ==========================================================================
4093
- /**
4094
- * Add a create operation
4095
- * @param contentType - Content type to create
4096
- * @param data - Entry data with content field
4097
- * @param id - Optional operation ID for tracking
4098
- */
4099
- create(contentType, data, id) {
4100
- this.operations.push({
4101
- id,
4102
- method: "POST",
4103
- path: `/${contentType}`,
4104
- body: this.localized(data)
4105
- });
4106
- return this;
4107
- }
4108
- /**
4109
- * Add an update operation (partial update).
4110
- *
4111
- * The submitted fields are merged into the entry and the rest are left as they are. The merge is
4112
- * field-level rather than deep: sending an object-typed field replaces that field's whole value.
4113
- *
4114
- * @param contentType - Content type
4115
- * @param entryId - Entry ID to update
4116
- * @param data - Fields to update
4117
- * @param id - Optional operation ID for tracking
4118
- */
4119
- update(contentType, entryId, data, id) {
4120
- this.operations.push({
4121
- id,
4122
- method: "PATCH",
4123
- path: `/${contentType}/${entryId}`,
4124
- body: this.localized(data)
4125
- });
4126
- return this;
4127
- }
4128
- /**
4129
- * Add a replace operation (full replacement).
4130
- *
4131
- * The payload becomes the entry's content for the addressed locale: fields you do not send are
4132
- * cleared. Send the whole entry, or use {@link BatchClient.update} to change part of one.
4133
- *
4134
- * @param contentType - Content type
4135
- * @param entryId - Entry ID to replace
4136
- * @param data - Complete entry data
4137
- * @param id - Optional operation ID for tracking
4138
- */
4139
- replace(contentType, entryId, data, id) {
4140
- this.operations.push({
4141
- id,
4142
- method: "PUT",
4143
- path: `/${contentType}/${entryId}`,
4144
- body: this.localized(data)
4145
- });
4146
- return this;
4147
- }
4148
- /**
4149
- * Add a delete operation
4150
- * @param contentType - Content type
4151
- * @param entryId - Entry ID to delete
4152
- * @param id - Optional operation ID for tracking
4153
- */
4154
- delete(contentType, entryId, id) {
4155
- this.operations.push({
4156
- id,
4157
- method: "DELETE",
4158
- path: `/${contentType}/${entryId}`
4159
- });
4160
- return this;
4161
- }
4162
- // ==========================================================================
4163
- // SINGLETON OPERATIONS
4164
- // ==========================================================================
4165
- /**
4166
- * Add a singleton upsert operation
4167
- * @param singletonKey - Singleton key
4168
- * @param data - Singleton content
4169
- * @param id - Optional operation ID for tracking
4170
- */
4171
- upsertSingleton(singletonKey, data, id) {
4172
- this.operations.push({
4173
- id,
4174
- method: "PATCH",
4175
- path: `/_singletons/${singletonKey}`,
4176
- body: this.localized(data)
4177
- });
4178
- return this;
4179
- }
4180
- /**
4181
- * Add a singleton create operation (alias for upsert with POST)
4182
- * @param singletonKey - Singleton key
4183
- * @param data - Singleton content
4184
- * @param id - Optional operation ID for tracking
4185
- */
4186
- createSingleton(singletonKey, data, id) {
4187
- this.operations.push({
4188
- id,
4189
- method: "POST",
4190
- path: `/_singletons/${singletonKey}`,
4191
- body: this.localized(data)
4192
- });
4193
- return this;
4194
- }
4195
- // ==========================================================================
4196
- // RAW OPERATIONS
4197
- // ==========================================================================
4198
- /**
4199
- * Add a raw operation (for advanced use cases). It is sent exactly as written: the client's
4200
- * locale is not added to it.
4201
- * @param operation - The operation to add
4202
- */
4203
- raw(operation) {
4204
- this.operations.push(operation);
4205
- return this;
4206
- }
4207
- /**
4208
- * Add multiple raw operations
4209
- * @param operations - Operations to add
4210
- */
4211
- rawMany(operations) {
4212
- this.operations.push(...operations);
4213
- return this;
4214
- }
4215
- // ==========================================================================
4216
- // BULK HELPERS
4217
- // ==========================================================================
4218
- /**
4219
- * Bulk create multiple entries of the same content type
4220
- * @param contentType - Content type
4221
- * @param items - Array of entry data
4222
- */
4223
- bulkCreate(contentType, items) {
4224
- for (const item of items) {
4225
- const { id, ...data } = item;
4226
- this.create(contentType, data, id);
4227
- }
4228
- return this;
4229
- }
4230
- /**
4231
- * Bulk update multiple entries
4232
- * @param contentType - Content type
4233
- * @param updates - Array of { entryId, data } pairs
4234
- */
4235
- bulkUpdate(contentType, updates) {
4236
- for (const update of updates) {
4237
- this.update(contentType, update.entryId, update.data, update.id);
4238
- }
4239
- return this;
4240
- }
4241
- /**
4242
- * Bulk delete multiple entries
4243
- * @param contentType - Content type
4244
- * @param entryIds - Entry IDs to delete
4245
- */
4246
- bulkDelete(contentType, entryIds) {
4247
- for (const entryId of entryIds) {
4248
- this.delete(contentType, entryId);
4249
- }
4250
- return this;
4251
- }
4252
- // ==========================================================================
4253
- // CONFIGURATION
4254
- // ==========================================================================
4255
- /**
4256
- * Configure default options for execution
4257
- * @param options - Batch options
4258
- */
4259
- configure(options) {
4260
- this.defaultOptions = { ...this.defaultOptions, ...options };
4261
- return this;
4262
- }
4263
- /**
4264
- * Enable atomic mode (all-or-nothing)
4265
- */
4266
- atomic() {
4267
- this.defaultOptions.atomic = true;
4268
- return this;
4269
- }
4270
- /**
4271
- * Enable dry-run mode (validate without executing)
4272
- */
4273
- dryRun() {
4274
- this.defaultOptions.dryRun = true;
4275
- return this;
4276
- }
4277
- /**
4278
- * Disable returning full data in response
4279
- */
4280
- noData() {
4281
- this.defaultOptions.returnData = false;
4282
- return this;
4283
- }
4284
- /**
4285
- * Stop on first error (non-atomic mode)
4286
- */
4287
- stopOnError() {
4288
- this.defaultOptions.continueOnError = false;
4289
- return this;
4290
- }
4291
- /**
4292
- * Headers the batch request adds — e.g. `{ 'X-RelyIO-Dataset': 'staging' }` to write a dataset
4293
- * other than the client's. They replace the client's own header of the same name for this
4294
- * request alone, and apply to every operation in it: a batch is one request.
4295
- */
4296
- headers(headers) {
4297
- this.requestHeaders = { ...this.requestHeaders, ...headers };
4298
- return this;
4299
- }
4300
- // ==========================================================================
4301
- // EXECUTION
4302
- // ==========================================================================
4303
- /**
4304
- * Get the current list of operations
4305
- */
4306
- getOperations() {
4307
- return [...this.operations];
4308
- }
4309
- /**
4310
- * Get the number of operations
4311
- */
4312
- size() {
4313
- return this.operations.length;
4314
- }
4315
- /**
4316
- * Clear all operations
4317
- */
4318
- clear() {
4319
- this.operations = [];
4320
- return this;
4321
- }
4322
- /**
4323
- * Clone the builder
4324
- */
4325
- clone() {
4326
- const cloned = new _BatchBuilder(this.httpClient, this.clientLocale);
4327
- cloned.operations = [...this.operations];
4328
- cloned.defaultOptions = { ...this.defaultOptions };
4329
- cloned.requestHeaders = this.requestHeaders === void 0 ? void 0 : { ...this.requestHeaders };
4330
- return cloned;
4331
- }
4332
- /**
4333
- * Execute the batch operations
4334
- * @param options - Override default options
4335
- */
4336
- async execute(options) {
4337
- if (this.operations.length === 0) {
4338
- throw new Error(
4339
- "No operations to execute. Add operations using .create(), .update(), .delete(), etc."
4340
- );
4341
- }
4342
- if (this.operations.length > 100) {
4343
- throw new Error("Maximum 100 operations per batch. Split into multiple batches.");
4344
- }
4345
- const finalOptions = { ...this.defaultOptions, ...options };
4346
- return this.httpClient.fetchPayload("/v1/batch", "post", {
4347
- body: { operations: this.operations, options: finalOptions },
4348
- headers: this.requestHeaders
4349
- });
4350
- }
4351
- /**
4352
- * Validate operations without executing (convenience for dry-run)
4353
- */
4354
- async validate() {
4355
- return this.execute({ dryRun: true });
4356
- }
4357
- /**
4358
- * Execute and throw if any operation failed
4359
- */
4360
- async executeOrThrow(options) {
4361
- const response = await this.execute(options);
4362
- if (response.summary.failed > 0) {
4363
- const failedOps = response.results.filter(
4364
- (result) => "error" in result
4365
- );
4366
- const errorMessages = failedOps.map(
4367
- (op) => `Operation ${op.index}${op.id != null && op.id !== "" ? ` (${op.id})` : ""}: ${op.error.code} \u2014 ${op.error.messages.join(" ")}`
4368
- ).join("; ");
4369
- throw new Error(
4370
- `Batch execution failed: ${response.summary.failed} of ${response.summary.total} operations failed. ${errorMessages}`
4371
- );
4372
- }
4373
- return response;
4374
- }
4375
- };
4376
- __name(_BatchBuilder, "BatchBuilder");
4377
- var BatchBuilder = _BatchBuilder;
4378
- var _BatchClient = class _BatchClient {
4379
- /**
4380
- * @param locale - The client's locale: the function `TypedRelyIOClient` hands over, or a locale
4381
- * fixed for this client
4382
- */
4383
- constructor(httpClient, locale) {
4384
- this.httpClient = httpClient;
4385
- /** The client's locale, handed to every batch this builds to ask as operations are added. */
4386
- __publicField(this, "clientLocale");
4387
- this.clientLocale = readClientLocale(locale);
4388
- }
4389
- /**
4390
- * Create a new batch builder, whose operations default to this client's locale
4391
- */
4392
- create() {
4393
- return new BatchBuilder(this.httpClient, this.clientLocale);
4394
- }
4395
- /**
4396
- * Quick bulk create entries
4397
- * @param contentType - Content type
4398
- * @param items - Array of entry data
4399
- * @param options - Batch options
4400
- */
4401
- async bulkCreate(contentType, items, options) {
4402
- return this.create().bulkCreate(contentType, items).execute(options);
4403
- }
4404
- /**
4405
- * Quick bulk update entries
4406
- * @param contentType - Content type
4407
- * @param updates - Array of updates
4408
- * @param options - Batch options
4409
- */
4410
- async bulkUpdate(contentType, updates, options) {
4411
- return this.create().bulkUpdate(contentType, updates).execute(options);
4412
- }
4413
- /**
4414
- * Quick bulk delete entries
4415
- * @param contentType - Content type
4416
- * @param entryIds - Entry IDs to delete
4417
- * @param options - Batch options
4418
- */
4419
- async bulkDelete(contentType, entryIds, options) {
4420
- return this.create().bulkDelete(contentType, entryIds).execute(options);
4421
- }
4422
- /**
4423
- * Execute raw batch operations
4424
- * @param operations - Array of operations
4425
- * @param options - Batch options
4426
- */
4427
- async execute(operations, options) {
4428
- return this.create().rawMany(operations).execute(options);
4429
- }
4430
- };
4431
- __name(_BatchClient, "BatchClient");
4432
- var BatchClient = _BatchClient;
4433
-
4434
- // src/queries/adhoc-query-client.ts
4435
- function withClientLocale(parameters, clientLocale) {
4436
- if (clientLocale === void 0 || clientLocale === "" || parameters?.locale != null) {
4437
- return parameters;
4438
- }
4439
- return { ...parameters, locale: clientLocale };
4440
- }
4441
- __name(withClientLocale, "withClientLocale");
4442
- var _AdhocQueryBuilder = class _AdhocQueryBuilder {
4443
- /**
4444
- * @param locale - The client's locale (`withLocale()`, `updateConfig({ locale })`), sent as the
4445
- * `locale` parameter when `.param('locale', …)` binds none: the function `TypedRelyIOClient`
4446
- * hands over, or a locale fixed for this builder
4447
- */
4448
- constructor(httpClient, locale) {
4449
- this.httpClient = httpClient;
4450
- __publicField(this, "queryDef", {});
4451
- __publicField(this, "parameters", {});
4452
- __publicField(this, "options", {});
4453
- /** The client's locale, asked when the query is sent rather than copied when it was built. */
4454
- __publicField(this, "clientLocale");
4455
- this.clientLocale = readClientLocale(locale);
4456
- }
4457
- // ==========================================================================
4458
- // QUERY BLOCKS
4459
- // ==========================================================================
4460
- /**
4461
- * Add a fetch block to retrieve content
4462
- * @param config - Fetch configuration
4463
- */
4464
- fetch(config) {
4465
- this.queryDef.fetch = config;
4466
- return this;
4467
- }
4468
- /**
4469
- * Fetch from a content type
4470
- * @param contentType - Content type to fetch
4471
- */
4472
- from(contentType) {
4473
- this.queryDef.fetch = { ...this.queryDef.fetch, contentType };
4474
- return this;
4475
- }
4476
- /**
4477
- * Fetch a singleton
4478
- * @param singletonKey - Singleton key
4479
- */
4480
- singleton(singletonKey) {
4481
- this.queryDef.fetch = { ...this.queryDef.fetch, singleton: singletonKey };
4482
- return this;
4483
- }
4484
- /**
4485
- * Add aggregate block
4486
- * @param config - Aggregate configuration
4487
- */
4488
- aggregate(config) {
4489
- this.queryDef.aggregate = config;
4490
- return this;
4491
- }
4492
- /**
4493
- * Add search block
4494
- * @param config - Search configuration
4495
- */
4496
- search(config) {
4497
- this.queryDef.search = config;
4498
- return this;
4499
- }
4500
- // ==========================================================================
4501
- // FETCH MODIFIERS
4502
- // ==========================================================================
4503
- /**
4504
- * Add where conditions to fetch
4505
- * @param conditions - Filter conditions
4506
- */
4507
- where(conditions) {
4508
- this.queryDef.fetch = {
4509
- ...this.queryDef.fetch,
4510
- where: { ...this.queryDef.fetch?.where, ...conditions }
4511
- };
4512
- return this;
4513
- }
4514
- /**
4515
- * Add include for reference population
4516
- * @param fields - Reference fields to include
4517
- */
4518
- include(fields) {
4519
- this.queryDef.fetch = {
4520
- ...this.queryDef.fetch,
4521
- include: fields
4522
- };
4523
- return this;
4524
- }
4525
- /**
4526
- * Add sort specification
4527
- * @param sort - Sort configuration
4528
- */
4529
- sort(sort) {
4530
- this.queryDef.fetch = {
4531
- ...this.queryDef.fetch,
4532
- sort: { ...this.queryDef.fetch?.sort, ...sort }
4533
- };
4534
- return this;
4535
- }
4536
- /**
4537
- * Sort by field ascending
4538
- * @param field - Field to sort by
4539
- */
4540
- sortAsc(field) {
4541
- return this.sort({ [field]: "asc" });
4542
- }
4543
- /**
4544
- * Sort by field descending
4545
- * @param field - Field to sort by
4546
- */
4547
- sortDesc(field) {
4548
- return this.sort({ [field]: "desc" });
4549
- }
4550
- /**
4551
- * Limit results
4552
- * @param count - Maximum results
4553
- */
4554
- limit(count) {
4555
- this.queryDef.fetch = {
4556
- ...this.queryDef.fetch,
4557
- limit: count
4558
- };
4559
- return this;
4560
- }
4561
- /**
4562
- * Skip results
4563
- * @param count - Number to skip
4564
- */
4565
- offset(count) {
4566
- this.queryDef.fetch = {
4567
- ...this.queryDef.fetch,
4568
- offset: count
4569
- };
4570
- return this;
4571
- }
4572
- // ==========================================================================
4573
- // PROJECTIONS & FILTERS
4574
- // ==========================================================================
4575
- /**
4576
- * Select and transform fields in output
4577
- * @param projection - Field mapping (outputField: sourceField)
4578
- */
4579
- select(projection) {
4580
- this.queryDef.select = { ...this.queryDef.select, ...projection };
4581
- return this;
4582
- }
4583
- /**
4584
- * Add a post-fetch filter (supports ${param} syntax)
4585
- * @param field - Field path to filter
4586
- * @param condition - Filter condition
4587
- */
4588
- filter(field, condition) {
4589
- this.queryDef.filters = { ...this.queryDef.filters, [field]: condition };
4590
- return this;
4591
- }
4592
- /**
4593
- * Add full-text search
4594
- * @param config - Full-text configuration
4595
- */
4596
- fullText(config) {
4597
- this.queryDef.fullText = config;
4598
- return this;
4599
- }
4600
- // ==========================================================================
4601
- // PARAMETERS
4602
- // ==========================================================================
4603
- /**
4604
- * Set query parameters
4605
- * @param params - Parameter values
4606
- */
4607
- params(params) {
4608
- this.parameters = { ...this.parameters, ...params };
4609
- return this;
4610
- }
4611
- /**
4612
- * Set a single parameter
4613
- * @param name - Parameter name
4614
- * @param value - Parameter value
4615
- */
4616
- param(name, value) {
4617
- this.parameters[name] = value;
4618
- return this;
4619
- }
4620
- // ==========================================================================
4621
- // OPTIONS
4622
- // ==========================================================================
4623
- /**
4624
- * Enable debug mode
4625
- */
4626
- debug() {
4627
- this.options.debug = true;
4628
- return this;
4629
- }
4630
- /**
4631
- * Skip cache for this request
4632
- */
4633
- skipCache() {
4634
- this.options.skipCache = true;
4635
- return this;
4636
- }
4637
- /**
4638
- * Set custom timeout
4639
- * @param ms - Timeout in milliseconds (1000-60000)
4640
- */
4641
- timeout(ms) {
4642
- if (ms < 1e3 || ms > 6e4) {
4643
- throw new Error("Timeout must be between 1000 and 60000 milliseconds");
4644
- }
4645
- this.options.timeout = ms;
4646
- return this;
4647
- }
4648
- /**
4649
- * Set cache policy
4650
- * @param ttl - Cache TTL in seconds
4651
- * @param strategy - Cache strategy
4652
- */
4653
- cache(ttl, strategy) {
4654
- this.queryDef.cache = { ttl, strategy };
4655
- return this;
4656
- }
4657
- // ==========================================================================
4658
- // EXECUTION
4659
- // ==========================================================================
4660
- /**
4661
- * Get the query definition (for debugging)
4662
- */
4663
- getDefinition() {
4664
- return { ...this.queryDef };
4665
- }
4666
- /**
4667
- * Get the parameters (for debugging)
4668
- */
4669
- getParameters() {
4670
- return { ...this.parameters };
4671
- }
2762
+ // src/content/singleton-client.ts
2763
+ var _SingletonClient = class _SingletonClient extends DocumentHandle {
4672
2764
  /**
4673
- * Execute the query
2765
+ * The singleton, or `null` when nobody has written it yet (REL-2419, REL-2409 Q18).
2766
+ *
2767
+ * `null` only for a singleton with no content: one the schema does not declare, or a dataset with
2768
+ * no schema deployed, throws `ResourceNotFoundError` naming which (`context.resourceType`
2769
+ * `'singleton'` or `'schema'`), as a content type's `get` does.
2770
+ *
2771
+ * It takes a content read's options where the route declares them — `include`, `locale` (one
2772
+ * code), `dataset` and `fresh` (`DocumentReadOptions`).
2773
+ *
2774
+ * @example
2775
+ * ```typescript
2776
+ * const home = await relyio.singleton('homepage').get({ include: ['hero'], locale: 'fr-FR' })
2777
+ * if (home) await relyio.singleton('homepage').update(home, { headline: 'Bonjour' })
2778
+ * ```
4674
2779
  */
4675
- async execute() {
4676
- if (!this.queryDef.fetch && !this.queryDef.aggregate && !this.queryDef.search) {
4677
- throw new Error(
4678
- "Query must have at least one of: fetch, aggregate or search. Use .from(), .aggregate() or .search()"
4679
- );
4680
- }
4681
- const body = {
4682
- query: this.queryDef
4683
- };
4684
- const parameters = withClientLocale(this.parameters, this.clientLocale());
4685
- if (parameters !== void 0 && Object.keys(parameters).length > 0) {
4686
- body.parameters = parameters;
4687
- }
4688
- if (Object.keys(this.options).length > 0) {
4689
- body.options = this.options;
2780
+ async get(options = {}) {
2781
+ try {
2782
+ return await this.httpClient.fetchPayload("/v1/singletons/{key}", "get", {
2783
+ path: { key: this.name },
2784
+ query: documentQuery(options, this.clientLocale()),
2785
+ headers: readHeaders(options)
2786
+ });
2787
+ } catch (error) {
2788
+ if (isMissingEntry(error)) return null;
2789
+ throw error;
4690
2790
  }
2791
+ }
2792
+ /** The API publishes on PATCH: there is no separate publish route. */
2793
+ patch(write, options) {
4691
2794
  return this.httpClient.fetchPayload(
4692
- "/v1/query",
4693
- "post",
4694
- { body }
2795
+ "/v1/singletons/{key}",
2796
+ "patch",
2797
+ {
2798
+ path: { key: this.name },
2799
+ body: DocumentHandle.bodyOf(write),
2800
+ headers: DocumentHandle.headersOf(write, options)
2801
+ }
4695
2802
  );
4696
2803
  }
4697
- /**
4698
- * Execute and return the records as an array (for list queries).
4699
- */
4700
- async items() {
4701
- const { result } = await this.execute();
4702
- if (result === void 0) return [];
4703
- return Array.isArray(result) ? result : [result];
4704
- }
4705
- /**
4706
- * Execute and return the single record (for singleton queries).
4707
- */
4708
- async data() {
4709
- const { result } = await this.execute();
4710
- if (Array.isArray(result)) return result[0];
4711
- return result;
2804
+ async readVersions(query) {
2805
+ return this.httpClient.fetchPayload("/v1/singletons/{key}/versions", "get", { path: { key: this.name }, query });
4712
2806
  }
4713
- /**
4714
- * Clone the builder
4715
- */
4716
- clone() {
4717
- const cloned = new _AdhocQueryBuilder(this.httpClient, this.clientLocale);
4718
- cloned.queryDef = JSON.parse(JSON.stringify(this.queryDef));
4719
- cloned.parameters = { ...this.parameters };
4720
- cloned.options = { ...this.options };
4721
- return cloned;
2807
+ readVersion(versionNumber, query) {
2808
+ return this.httpClient.fetchPayload(
2809
+ "/v1/singletons/{key}/versions/{versionNumber}",
2810
+ "get",
2811
+ {
2812
+ path: { key: this.name, versionNumber },
2813
+ query
2814
+ }
2815
+ );
4722
2816
  }
4723
2817
  };
4724
- __name(_AdhocQueryBuilder, "AdhocQueryBuilder");
4725
- var AdhocQueryBuilder = _AdhocQueryBuilder;
4726
- var _AdhocQueryClient = class _AdhocQueryClient {
4727
- /**
4728
- * @param locale - The client's locale (`withLocale()`, `updateConfig({ locale })`), sent as the
4729
- * `locale` parameter of every query that binds none: the function `TypedRelyIOClient` hands
4730
- * over, or a locale fixed for this client
4731
- */
4732
- constructor(httpClient, locale) {
4733
- this.httpClient = httpClient;
4734
- /** The client's locale, asked on every query rather than copied when this client was taken. */
4735
- __publicField(this, "clientLocale");
4736
- this.clientLocale = readClientLocale(locale);
4737
- }
2818
+ __name(_SingletonClient, "SingletonClient");
2819
+ var SingletonClient = _SingletonClient;
2820
+
2821
+ // src/content/settings-client.ts
2822
+ var _SettingsClient = class _SettingsClient extends DocumentHandle {
4738
2823
  /**
4739
- * Create a new query builder
2824
+ * The settings group (REL-2419, REL-2409 Q18).
2825
+ *
2826
+ * Never `null`: the API answers a declared group that nobody has written yet with its declared
2827
+ * defaults (it initializes the group on that first read), so there is no missing group to report.
2828
+ * A group the schema does not declare, or a dataset with no schema deployed, throws
2829
+ * `ResourceNotFoundError` naming which (`context.resourceType` `'settingsGroup'` or `'schema'`) —
2830
+ * the not-found rule every handle's `get` follows.
2831
+ *
2832
+ * It takes a content read's options where the route declares them — `include`, `locale` (one
2833
+ * code), `dataset` and `fresh` (`DocumentReadOptions`).
2834
+ *
2835
+ * @example
2836
+ * ```typescript
2837
+ * const site = await relyio.settings('site').get({ include: ['logo'] })
2838
+ * await relyio.settings('site').update(site, { title: 'New title' })
2839
+ * ```
4740
2840
  */
4741
- build() {
4742
- return new AdhocQueryBuilder(this.httpClient, this.clientLocale);
2841
+ async get(options = {}) {
2842
+ return this.httpClient.fetchPayload(
2843
+ "/v1/settings/{groupId}",
2844
+ "get",
2845
+ {
2846
+ path: { groupId: this.name },
2847
+ query: documentQuery(options, this.clientLocale()),
2848
+ headers: readHeaders(options)
2849
+ }
2850
+ );
4743
2851
  }
4744
- /**
4745
- * Execute a query definition directly
4746
- * @param definition - Query definition
4747
- * @param parameters - Parameter values
4748
- * @param options - Execution options
4749
- * @param request - `headers` this one request adds to the client's own, e.g. `X-RelyIO-Dataset`
4750
- * to query another dataset the key can see. A name the client already sends is replaced for
4751
- * this request only.
4752
- */
4753
- async execute(definition, parameters, options, request) {
4754
- const body = {
4755
- query: definition
4756
- };
4757
- const sent = withClientLocale(parameters, this.clientLocale());
4758
- if (sent !== void 0 && Object.keys(sent).length > 0) {
4759
- body.parameters = sent;
4760
- }
4761
- if (options && Object.keys(options).length > 0) {
4762
- body.options = options;
4763
- }
2852
+ async reset(...args) {
2853
+ const [first, second] = args;
2854
+ const write = first !== void 0 && isRead(first) ? { ...versionCheckOf(first), options: second } : { locale: this.localeOf(first?.locale), ifMatch: void 0, options: first };
4764
2855
  return this.httpClient.fetchPayload(
4765
- "/v1/query",
2856
+ "/v1/settings/{groupId}/reset",
4766
2857
  "post",
4767
- { body, headers: request?.headers }
2858
+ {
2859
+ path: { groupId: this.name },
2860
+ query: write.locale === void 0 ? void 0 : { locale: write.locale },
2861
+ headers: DocumentHandle.headersOf(write, write.options)
2862
+ }
4768
2863
  );
4769
2864
  }
4770
- /**
4771
- * Quick fetch helper
4772
- * @param contentType - Content type
4773
- * @param options - Fetch options
4774
- */
4775
- async fetch(contentType, options) {
4776
- const { result } = await this.execute({ fetch: { contentType, ...options } });
4777
- if (result === void 0) return [];
4778
- return Array.isArray(result) ? result : [result];
2865
+ /** The group's default values, as the schema declares them. */
2866
+ async getDefaults() {
2867
+ return this.httpClient.fetchPayload(
2868
+ "/v1/settings/{groupId}/defaults",
2869
+ "get",
2870
+ { path: { groupId: this.name } }
2871
+ );
4779
2872
  }
4780
- /**
4781
- * Quick singleton fetch helper
4782
- * @param singletonKey - Singleton key
4783
- */
4784
- async fetchSingleton(singletonKey) {
4785
- const { result } = await this.execute({ fetch: { singleton: singletonKey } });
4786
- if (Array.isArray(result)) return result[0];
4787
- return result;
2873
+ patch(write, options) {
2874
+ return this.httpClient.fetchPayload(
2875
+ "/v1/settings/{groupId}",
2876
+ "patch",
2877
+ {
2878
+ path: { groupId: this.name },
2879
+ body: DocumentHandle.bodyOf(write),
2880
+ headers: DocumentHandle.headersOf(write, options)
2881
+ }
2882
+ );
4788
2883
  }
4789
- /**
4790
- * Quick aggregate helper
4791
- * @param source - Source content type
4792
- * @param metrics - Metrics to compute
4793
- * @param groupBy - Optional grouping field
4794
- */
4795
- async aggregate(source, metrics, groupBy) {
4796
- const { result } = await this.execute({ aggregate: { source, metrics, by: groupBy } });
4797
- if (result === void 0) return [];
4798
- return Array.isArray(result) ? result : [result];
2884
+ async readVersions(query) {
2885
+ return this.httpClient.fetchPayload("/v1/settings/{groupId}/versions", "get", { path: { groupId: this.name }, query });
2886
+ }
2887
+ readVersion(versionNumber, query) {
2888
+ return this.httpClient.fetchPayload("/v1/settings/{groupId}/versions/{versionNumber}", "get", {
2889
+ path: { groupId: this.name, versionNumber },
2890
+ query
2891
+ });
4799
2892
  }
4800
2893
  };
4801
- __name(_AdhocQueryClient, "AdhocQueryClient");
4802
- var AdhocQueryClient = _AdhocQueryClient;
2894
+ __name(_SettingsClient, "SettingsClient");
2895
+ var SettingsClient = _SettingsClient;
4803
2896
 
4804
2897
  // src/changes/changes.ts
4805
2898
  var CHANGES_POLL_TIMEOUT_MS = 25e3 + 5e3;
@@ -5436,6 +3529,264 @@ function hasOwn(object, key) {
5436
3529
  }
5437
3530
  __name(hasOwn, "hasOwn");
5438
3531
 
3532
+ // src/search/search-options.ts
3533
+ function scopeOf(contentType) {
3534
+ if (contentType === void 0) return { contentTypes: "*" };
3535
+ if (typeof contentType === "string") return { contentType };
3536
+ return { contentTypes: [...contentType] };
3537
+ }
3538
+ __name(scopeOf, "scopeOf");
3539
+ function selectedFields(select) {
3540
+ if (select === void 0 || select.length === 0) return {};
3541
+ return { fields: Object.fromEntries(select.map((field) => [field, field])) };
3542
+ }
3543
+ __name(selectedFields, "selectedFields");
3544
+ function localeOf(own, client) {
3545
+ const locale = own ?? client;
3546
+ return locale === void 0 || locale === "" ? {} : { locale };
3547
+ }
3548
+ __name(localeOf, "localeOf");
3549
+ function whereOf(where) {
3550
+ if (where === void 0) return {};
3551
+ refuseMetadataConditions(where);
3552
+ return Object.keys(where).length === 0 ? {} : { where };
3553
+ }
3554
+ __name(whereOf, "whereOf");
3555
+ function given(options) {
3556
+ return Object.fromEntries(Object.entries(options).filter(([, value]) => value !== void 0));
3557
+ }
3558
+ __name(given, "given");
3559
+ function transportOf(options) {
3560
+ const headers = readHeaders(options);
3561
+ return {
3562
+ ...headers === void 0 ? {} : { headers },
3563
+ ...typeof options.fresh === "string" ? { query: { [FRESH_PARAMETER]: options.fresh } } : {}
3564
+ };
3565
+ }
3566
+ __name(transportOf, "transportOf");
3567
+ async function runSearch(http, q, options, clientLocale) {
3568
+ const sort = options.sort === void 0 ? [] : Object.entries(options.sort).flatMap(
3569
+ ([field, direction]) => direction === void 0 ? [] : [{ field, direction }]
3570
+ );
3571
+ const body = {
3572
+ q,
3573
+ ...scopeOf(options.contentType),
3574
+ ...whereOf(options.where),
3575
+ ...given({
3576
+ searchFields: options.searchFields === void 0 ? void 0 : [...options.searchFields]
3577
+ }),
3578
+ ...selectedFields(options.select),
3579
+ ...sort.length === 0 ? {} : { sort },
3580
+ ...given({
3581
+ page: options.page,
3582
+ limit: options.limit,
3583
+ includeScore: options.includeScore,
3584
+ explain: options.explain,
3585
+ highlight: options.highlight,
3586
+ facets: options.facets
3587
+ }),
3588
+ ...localeOf(options.locale, clientLocale)
3589
+ };
3590
+ const response = await http.send(
3591
+ "/v1/search",
3592
+ "post",
3593
+ {
3594
+ body,
3595
+ ...transportOf(options)
3596
+ }
3597
+ );
3598
+ return response.body;
3599
+ }
3600
+ __name(runSearch, "runSearch");
3601
+ async function runAutocomplete(http, q, options, clientLocale) {
3602
+ const body = {
3603
+ q,
3604
+ contentType: options.contentType,
3605
+ field: options.field,
3606
+ ...given({ fuzzy: options.fuzzy }),
3607
+ ...whereOf(options.where),
3608
+ ...given({ limit: options.limit }),
3609
+ ...localeOf(options.locale, clientLocale)
3610
+ };
3611
+ const response = await http.send(
3612
+ "/v1/search/autocomplete",
3613
+ "post",
3614
+ { body, ...transportOf(options) }
3615
+ );
3616
+ return response.body;
3617
+ }
3618
+ __name(runAutocomplete, "runAutocomplete");
3619
+ async function runSimilar(http, source, options, clientLocale) {
3620
+ const body = {
3621
+ source,
3622
+ ...scopeOf(options.contentType),
3623
+ ...given({ limit: options.limit }),
3624
+ ...localeOf(options.locale, clientLocale),
3625
+ ...selectedFields(options.select)
3626
+ };
3627
+ const response = await http.send(
3628
+ "/v1/search/similar",
3629
+ "post",
3630
+ { body, ...transportOf(options) }
3631
+ );
3632
+ return response.body;
3633
+ }
3634
+ __name(runSimilar, "runSimilar");
3635
+
3636
+ // src/batch/batch-operations.ts
3637
+ var VERBS = ["create", "update", "replace", "delete", "singleton"];
3638
+ async function runBatch(http, operations, options, clientLocale) {
3639
+ const { atomic, dryRun, stopOnError, returnData, dataset } = options;
3640
+ const runOptions = {
3641
+ ...atomic === void 0 ? {} : { atomic },
3642
+ ...dryRun === void 0 ? {} : { dryRun },
3643
+ ...stopOnError === void 0 ? {} : { continueOnError: !stopOnError },
3644
+ ...returnData === void 0 ? {} : { returnData }
3645
+ };
3646
+ const headers = readHeaders({ ...dataset === void 0 ? {} : { dataset } });
3647
+ const response = await http.send("/v1/batch", "post", {
3648
+ body: {
3649
+ operations: operations.map((operation, index) => operationOf(operation, index, clientLocale)),
3650
+ ...Object.keys(runOptions).length === 0 ? {} : { options: runOptions }
3651
+ },
3652
+ ...headers === void 0 ? {} : { headers }
3653
+ });
3654
+ return response.body;
3655
+ }
3656
+ __name(runBatch, "runBatch");
3657
+ function operationOf(operation, index, clientLocale) {
3658
+ const named = VERBS.filter((verb2) => operation[verb2] !== void 0);
3659
+ const verb = named[0];
3660
+ if (verb === void 0 || named.length > 1) {
3661
+ throw new ValidationError(
3662
+ `Batch operation ${index} must name exactly one of ${VERBS.join(", ")}; it names ${named.length === 0 ? "none" : named.join(" and ")}.`,
3663
+ [{ field: `operations[${index}]`, code: "ONE_VERB", message: "name one verb" }]
3664
+ );
3665
+ }
3666
+ const { slug, parentId, sortOrder } = operation;
3667
+ const placement = {
3668
+ ...slug === void 0 ? {} : { slug },
3669
+ ...parentId === void 0 ? {} : { parentId },
3670
+ ...sortOrder === void 0 ? {} : { sortOrder }
3671
+ };
3672
+ const located = /* @__PURE__ */ __name((locale) => locale === void 0 ? {} : { locale }, "located");
3673
+ switch (verb) {
3674
+ case "create": {
3675
+ return {
3676
+ method: "POST",
3677
+ path: `/${operation.create}`,
3678
+ body: {
3679
+ content: operation.data,
3680
+ ...placement,
3681
+ ...located(callLocale(operation.locale, clientLocale))
3682
+ }
3683
+ };
3684
+ }
3685
+ case "update":
3686
+ case "replace": {
3687
+ const target = targetOf(operation, clientLocale);
3688
+ return {
3689
+ method: verb === "update" ? "PATCH" : "PUT",
3690
+ path: `/${operation[verb]}/${target.id}`,
3691
+ ...target.ifMatch === void 0 ? {} : { ifMatch: target.ifMatch },
3692
+ body: { content: operation.data, ...placement, ...located(target.locale) }
3693
+ };
3694
+ }
3695
+ case "delete": {
3696
+ const target = targetOf(operation, void 0);
3697
+ return {
3698
+ method: "DELETE",
3699
+ path: `/${operation.delete}/${target.id}`,
3700
+ ...target.ifMatch === void 0 ? {} : { ifMatch: target.ifMatch }
3701
+ };
3702
+ }
3703
+ case "singleton": {
3704
+ return {
3705
+ method: "PATCH",
3706
+ path: `/_singletons/${operation.singleton}`,
3707
+ body: { content: operation.data, ...located(callLocale(operation.locale, clientLocale)) }
3708
+ };
3709
+ }
3710
+ }
3711
+ }
3712
+ __name(operationOf, "operationOf");
3713
+ function targetOf(operation, clientLocale) {
3714
+ if (operation.entry !== void 0) return versionCheckOf(operation.entry);
3715
+ if (operation.id === void 0) {
3716
+ throw new ValidationError("A batch update, replace or delete names an `entry` or an `id`.", [
3717
+ { field: "id", code: "MISSING_TARGET", message: "name an entry or an id" }
3718
+ ]);
3719
+ }
3720
+ const locale = callLocale(operation.locale, clientLocale);
3721
+ return { id: operation.id, ...locale === void 0 ? {} : { locale } };
3722
+ }
3723
+ __name(targetOf, "targetOf");
3724
+
3725
+ // src/queries/query-call.ts
3726
+ async function runAdhocQuery(http, query, clientLocale) {
3727
+ const { where, include, select, sort, limit, page, aggregate, fullText } = query;
3728
+ if (where !== void 0) refuseMetadataConditions(where);
3729
+ const order = sort === void 0 ? [] : Object.entries(sort).flatMap(
3730
+ ([field, direction]) => direction === void 0 ? [] : [{ [field]: direction }]
3731
+ );
3732
+ const fetch2 = {
3733
+ contentType: query.from,
3734
+ ...where === void 0 || Object.keys(where).length === 0 ? {} : { where },
3735
+ ...include === void 0 || include.length === 0 ? {} : { include: [...include] },
3736
+ ...order.length === 0 ? {} : { sort: order },
3737
+ ...limit === void 0 ? {} : { limit },
3738
+ ...page === void 0 ? {} : { offset: offsetOf(page, limit) }
3739
+ };
3740
+ const locale = query.locale ?? clientLocale;
3741
+ const parameters = {
3742
+ ...query.params,
3743
+ ...locale === void 0 || locale === "" ? {} : { locale }
3744
+ };
3745
+ const body = {
3746
+ query: {
3747
+ fetch: fetch2,
3748
+ ...aggregate === void 0 ? {} : { aggregate },
3749
+ ...select === void 0 || select.length === 0 ? {} : { select: Object.fromEntries(select.map((field) => [field, field])) },
3750
+ ...fullText === void 0 ? {} : { fullText }
3751
+ },
3752
+ ...Object.keys(parameters).length === 0 ? {} : { parameters }
3753
+ };
3754
+ const headers = readHeaders(query);
3755
+ const response = await http.send("/v1/query", "post", {
3756
+ body,
3757
+ ...headers === void 0 ? {} : { headers },
3758
+ ...typeof query.fresh === "string" ? { query: { [FRESH_PARAMETER]: query.fresh } } : {}
3759
+ });
3760
+ return response.body;
3761
+ }
3762
+ __name(runAdhocQuery, "runAdhocQuery");
3763
+ function offsetOf(page, limit) {
3764
+ if (limit === void 0 || !Number.isInteger(page) || page < 1) {
3765
+ throw new ValidationError(
3766
+ "`page` is a whole number from 1, beside the `limit` that sizes it.",
3767
+ [{ field: "page", code: "INVALID_PAGE", message: `page ${page}, limit ${String(limit)}` }]
3768
+ );
3769
+ }
3770
+ return (page - 1) * limit;
3771
+ }
3772
+ __name(offsetOf, "offsetOf");
3773
+ async function runNamedQuery(http, name, options, clientLocale) {
3774
+ const locale = options.locale ?? clientLocale;
3775
+ const headers = readHeaders(options);
3776
+ const response = await http.send(
3777
+ "/v1/queries/{name}",
3778
+ "post",
3779
+ {
3780
+ path: { name },
3781
+ body: options.params === void 0 ? {} : { params: options.params },
3782
+ ...locale === void 0 || locale === "" ? {} : { query: { locale } },
3783
+ ...headers === void 0 ? {} : { headers }
3784
+ }
3785
+ );
3786
+ return response.body;
3787
+ }
3788
+ __name(runNamedQuery, "runNamedQuery");
3789
+
5439
3790
  // src/config/dev-detection.ts
5440
3791
  function sandboxStateOf(resolution) {
5441
3792
  return resolution.state === "active" ? resolution.sandbox : resolution;
@@ -5640,9 +3991,9 @@ function withHandshake(configured) {
5640
3991
  return { config: merged, sandbox: sandboxStateOf(dev), configured };
5641
3992
  }
5642
3993
  __name(withHandshake, "withHandshake");
5643
- function withDefaults(base, given, defaults) {
3994
+ function withDefaults(base, given2, defaults) {
5644
3995
  const merged = new Map(Object.entries(base));
5645
- const givenValues = new Map(Object.entries(given ?? {}));
3996
+ const givenValues = new Map(Object.entries(given2 ?? {}));
5646
3997
  const defaultValues = new Map(Object.entries(defaults));
5647
3998
  for (const key of /* @__PURE__ */ new Set([...merged.keys(), ...givenValues.keys()])) {
5648
3999
  const fallback = merged.get(key);
@@ -5669,7 +4020,6 @@ function isOptionBag(value) {
5669
4020
  __name(isOptionBag, "isOptionBag");
5670
4021
 
5671
4022
  // src/client/create-client.ts
5672
- var API_VERSION_SEGMENT = "v1";
5673
4023
  var _TypedRelyIOClient = class _TypedRelyIOClient {
5674
4024
  constructor(config) {
5675
4025
  /**
@@ -5680,7 +4030,7 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
5680
4030
  __publicField(this, "config");
5681
4031
  /**
5682
4032
  * The configuration as `createClient()`, `setApiKey()` and `updateConfig()` gave it, before the
5683
- * `relyio dev` handshake: what the next update is laid over, and what `withLocale()` starts from.
4033
+ * `relyio dev` handshake: what the next update is laid over, and what `with()` starts from.
5684
4034
  */
5685
4035
  __publicField(this, "configured");
5686
4036
  __publicField(this, "_sandbox");
@@ -5690,7 +4040,15 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
5690
4040
  * rather than a copy of the locale, so one taken before `updateConfig({ locale })` sends the new
5691
4041
  * locale, or none after `locale: null` (REL-1453).
5692
4042
  */
5693
- __publicField(this, "currentLocale", /* @__PURE__ */ __name(() => this.config.locale, "currentLocale"));
4043
+ __publicField(this, "currentLocale", /* @__PURE__ */ __name(() => this.defaults.locale ?? (this.parent === void 0 ? this.config.locale : this.parent.currentLocale()), "currentLocale"));
4044
+ /**
4045
+ * The client `with()` was called on, for a client it made: whose key, base URL and configuration
4046
+ * this one reads with as they are when each request is sent. `undefined` for a client
4047
+ * `createClient()` made, which reads its own.
4048
+ */
4049
+ __publicField(this, "parent");
4050
+ /** What `with()` gave this client over its parent's: the locale and dataset it names, and no other. */
4051
+ __publicField(this, "defaults", {});
5694
4052
  /** The live edge's poll client id: chosen once, when first needed, and kept for this client's life. */
5695
4053
  __publicField(this, "_liveClientId");
5696
4054
  const { schema, ...clientConfig } = config;
@@ -5704,12 +4062,6 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
5704
4062
  // ============================================================================
5705
4063
  // CORE CLIENTS
5706
4064
  // ============================================================================
5707
- /**
5708
- * Get the schema definition
5709
- */
5710
- get schema() {
5711
- return this._schema;
5712
- }
5713
4065
  /**
5714
4066
  * The `relyio dev` sandbox this client resolved at construction, as the preview toolbar displays
5715
4067
  * it (`sdk-surfaces-spec.md` decision 22).
@@ -5725,13 +4077,23 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
5725
4077
  * ```
5726
4078
  */
5727
4079
  get sandbox() {
5728
- return this._sandbox;
4080
+ return this.parent === void 0 ? this._sandbox : this.parent.sandbox;
5729
4081
  }
5730
4082
  /**
5731
- * Get the ad-hoc query client. Every query it sends is in the client's locale as it is then.
4083
+ * Run a query in one call (REL-2421, REL-2409 Q22):
4084
+ *
4085
+ * - `query({ from, … })` runs one inline, with `list`'s option names — `where` (the query
4086
+ * route's dialect), `include`, `select`, `sort`, `limit`, `page`, `locale`, `dataset`, `fresh` —
4087
+ * plus `aggregate`, `params` and `fullText`;
4088
+ * - `query(name, { params, … })` runs the saved query the schema declares under `name`, its
4089
+ * `params` typed from that declaration.
4090
+ *
4091
+ * Both answer the API's body, `{ data: { result, metadata } }`. See {@link QueryCall}. Every query
4092
+ * is sent in the client's locale as it is then, unless it names its own.
5732
4093
  */
5733
4094
  get query() {
5734
- return new AdhocQueryClient(this.httpClient, this.currentLocale);
4095
+ const call = /* @__PURE__ */ __name((target, options = {}) => typeof target === "string" ? runNamedQuery(this.httpClient, target, options, this.currentLocale()) : runAdhocQuery(this.httpClient, target, this.currentLocale()), "call");
4096
+ return call;
5735
4097
  }
5736
4098
  // ============================================================================
5737
4099
  // CONTENT OPERATIONS
@@ -5742,12 +4104,11 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
5742
4104
  *
5743
4105
  * @example
5744
4106
  * ```typescript
5745
- * const products = await relyio.content('product')
5746
- * .query()
5747
- * .where({ status: 'active' }) // ← Field names autocomplete with schema!
5748
- * .locale('en-US')
5749
- * .limit(20)
5750
- * .execute()
4107
+ * const { data: products } = await relyio.content('product').list({
4108
+ * where: { status: 'active' }, // ← Field names autocomplete with schema!
4109
+ * locale: 'en-US',
4110
+ * limit: 20,
4111
+ * })
5751
4112
  * ```
5752
4113
  */
5753
4114
  content(contentType) {
@@ -5756,9 +4117,9 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
5756
4117
  /**
5757
4118
  * Get an asset client for a specific asset type.
5758
4119
  *
5759
- * Mirrors `content()` but targets `entryType: 'asset'` entries on the same
5760
- * dynamic `/{assetTypeId}` routes, and adds the `file(entryUid)` sub-resource
5761
- * builder + a high-level `upload()` helper. Type-safe when a schema is
4120
+ * Reads and writes as `content()` does — `list`, `get`, `listAll`, `update`, `delete` — over
4121
+ * `entryType: 'asset'` entries on the same dynamic `/{assetTypeId}` routes, and adds `create`,
4122
+ * the `file(entryUid)` sub-resource builder and a high-level `upload()` helper. Type-safe when a schema is
5762
4123
  * provided: autocompletes asset type names AND metadata field names.
5763
4124
  *
5764
4125
  * @example
@@ -5780,10 +4141,10 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
5780
4141
  *
5781
4142
  * @example
5782
4143
  * ```typescript
5783
- * const homepage = await relyio.singleton('homepage')
5784
- * .include(['featuredProducts'])
5785
- * .locale('en')
5786
- * .fetch()
4144
+ * const homepage = await relyio.singleton('homepage').get({
4145
+ * include: ['featuredProducts'],
4146
+ * locale: 'en',
4147
+ * })
5787
4148
  * ```
5788
4149
  */
5789
4150
  singleton(key) {
@@ -5795,101 +4156,54 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
5795
4156
  *
5796
4157
  * @example
5797
4158
  * ```typescript
5798
- * const emailConfig = await relyio.settings('email-config')
5799
- * .include(['defaultTemplate'])
5800
- * .fetch()
4159
+ * const emailConfig = await relyio.settings('email-config').get({
4160
+ * include: ['defaultTemplate'],
4161
+ * })
5801
4162
  * ```
5802
4163
  */
5803
4164
  settings(groupId) {
5804
4165
  return new SettingsClient(this.httpClient, groupId, this.currentLocale);
5805
4166
  }
5806
- // ============================================================================
5807
- // SEARCH OPERATIONS
5808
- // ============================================================================
5809
- /**
5810
- * Start a search query with full-text, semantic, or hybrid search.
5811
- *
5812
- * Where a schema types the client, the results are its entries — every content type's until
5813
- * `in()` or `across()` narrows them (`INV-SS-TYPES-INFERRED`).
5814
- *
5815
- * @example
5816
- * ```typescript
5817
- * const results = await relyio.search('wireless headphones')
5818
- * .in('product')
5819
- * .where({ status: 'active' })
5820
- * .limit(20)
5821
- * .includeScore()
5822
- * .execute()
5823
- * ```
5824
- */
5825
- search(query) {
5826
- return this.searchClient().search(query);
4167
+ search(query, options) {
4168
+ return runSearch(this.httpClient, query, options, this.currentLocale());
5827
4169
  }
5828
- /**
5829
- * Start an autocomplete query for search suggestions
5830
- *
5831
- * @example
5832
- * ```typescript
5833
- * const suggestions = await relyio.autocomplete('wire')
5834
- * .in('product')
5835
- * .field('title')
5836
- * .fuzzy()
5837
- * .limit(10)
5838
- * .execute()
5839
- * ```
5840
- */
5841
- autocomplete(query) {
5842
- return this.searchClient().autocomplete(query);
4170
+ autocomplete(query, options) {
4171
+ return runAutocomplete(this.httpClient, query, options, this.currentLocale());
5843
4172
  }
5844
- /**
5845
- * Find content similar to a given entry using vector search
5846
- *
5847
- * @example
5848
- * ```typescript
5849
- * const similar = await relyio.similar('entry_abc123')
5850
- * .in('product')
5851
- * .locale('fr-FR')
5852
- * .limit(5)
5853
- * .execute()
5854
- * ```
5855
- */
5856
- similar(sourceId) {
5857
- return this.searchClient().similar(sourceId);
4173
+ similar(sourceId, options) {
4174
+ return runSimilar(this.httpClient, sourceId, options, this.currentLocale());
5858
4175
  }
5859
4176
  // ============================================================================
5860
4177
  // BATCH OPERATIONS
5861
4178
  // ============================================================================
5862
4179
  /**
5863
- * Start a batch operation for bulk create/update/delete.
4180
+ * Write a list of operations in one request (REL-2421, REL-2409 Q22): each a create, update,
4181
+ * replace or delete of an entry, or a singleton write.
4182
+ *
4183
+ * An update, replace or delete takes the **entry you read** — checked against the version it
4184
+ * carries, in the locale it was read in, as `update(entry, data)` is — or an **id**, which writes
4185
+ * unchecked, on purpose. A stale entry fails its operation with `RESOURCE_CONFLICT` naming the
4186
+ * versions that disagreed: re-read it with `fresh: true` and retry. With `atomic: true` nothing
4187
+ * in the batch is written then — and the API takes an atomic batch only when every operation is
4188
+ * the same kind of write.
5864
4189
  *
5865
- * By default each operation lands or fails on its own. `atomic: true` makes the batch one
5866
- * transaction, which the API accepts only when every operation is the same kind of write and none
5867
- * writes a singleton that does not exist yet.
4190
+ * Answers the API's body, `{ data: { results, summary } }`: one result per operation, in order,
4191
+ * each an `outcome` or an `error`. An operation that failed is not thrown.
5868
4192
  *
5869
4193
  * @example
5870
4194
  * ```typescript
5871
- * const result = await relyio.batch()
5872
- * .create('product', { content: { title: 'iPhone', price: 999 } })
5873
- * .update('product', 'entry_abc', { content: { price: 899 } })
5874
- * .delete('product', 'entry_xyz')
5875
- * .upsertSingleton('homepage', { content: { hero: 'Welcome!' } })
5876
- * .execute()
5877
- *
5878
- * console.log(result.summary.succeeded)
5879
- *
5880
- * // All or nothing: one kind of write
5881
- * await relyio.batch()
5882
- * .update('product', 'entry_abc', { content: { price: 899 } })
5883
- * .update('product', 'entry_def', { content: { price: 799 } })
5884
- * .execute({ atomic: true })
4195
+ * const post = await relyio.content('post').get('hello-world')
4196
+ * const { data } = await relyio.batch([
4197
+ * { create: 'post', data: { title: 'New' } },
4198
+ * ...(post ? [{ update: 'post' as const, entry: post, data: { title: 'Changed' } }] : []),
4199
+ * { delete: 'post', id: 'entry_xyz' },
4200
+ * { singleton: 'homepage', data: { hero: 'Welcome!' } },
4201
+ * ])
4202
+ * console.log(data.summary.succeeded)
5885
4203
  * ```
5886
4204
  */
5887
- batch() {
5888
- return new BatchClient(this.httpClient, this.currentLocale).create();
5889
- }
5890
- /** The search client, whose builders send the client's locale as it is when each one is sent. */
5891
- searchClient() {
5892
- return new SearchClient(this.httpClient, this.currentLocale);
4205
+ async batch(operations, options = {}) {
4206
+ return runBatch(this.httpClient, operations, options, this.currentLocale());
5893
4207
  }
5894
4208
  // ============================================================================
5895
4209
  // CHANGES FEED
@@ -5909,7 +4223,7 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
5909
4223
  * const controller = new AbortController()
5910
4224
  * for await (const change of relyio.changes({ since: cursor, types: ['post'], signal: controller.signal })) {
5911
4225
  * // { entryId, contentType, locale, kind, cursor, fresh }
5912
- * await relyio.content(change.contentType).findById(change.entryId).fresh(change.fresh).execute()
4226
+ * await relyio.content(change.contentType).get(change.entryId, { fresh: change.fresh })
5913
4227
  * }
5914
4228
  * ```
5915
4229
  *
@@ -5951,7 +4265,7 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
5951
4265
  * signal: controller.signal,
5952
4266
  * // `fresh` names a position no cache holds a body for from before the change.
5953
4267
  * onChange: change =>
5954
- * relyio.content(change.contentType).findById(change.entryId).fresh(change.fresh).execute(),
4268
+ * relyio.content(change.contentType).get(change.entryId, { fresh: change.fresh }),
5955
4269
  * onResync: resync => refetchEverything(resync.cause === 'expired' ? resync.expired.fresh : undefined),
5956
4270
  * })
5957
4271
  * ```
@@ -5960,7 +4274,7 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
5960
4274
  * edge's URL. On a server, follow `changes()` with it instead.
5961
4275
  */
5962
4276
  liveChanges(options = {}) {
5963
- const { apiKey, dataset, baseUrl, liveUrl, fetch: fetch2, webSocket, timeout } = this.config;
4277
+ const { apiKey, dataset, baseUrl, liveUrl, fetch: fetch2, webSocket, timeout } = this.getConfig();
5964
4278
  this._liveClientId ?? (this._liveClientId = randomClientId());
5965
4279
  return subscribeLiveEdge(
5966
4280
  {
@@ -5988,60 +4302,13 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
5988
4302
  * @example
5989
4303
  * ```typescript
5990
4304
  * const since = await relyio.changesHead()
5991
- * const post = await relyio.content('post').findById(id).execute()
4305
+ * const post = await relyio.content('post').get(id)
5992
4306
  * for await (const change of relyio.changes({ since, ids: [id] })) { … }
5993
4307
  * ```
5994
4308
  */
5995
4309
  changesHead(options = {}) {
5996
4310
  return readChangesHead(this.httpClient, options);
5997
4311
  }
5998
- // ============================================================================
5999
- // QUERY OPERATIONS
6000
- // ============================================================================
6001
- /**
6002
- * Build and execute an ad-hoc query dynamically
6003
- *
6004
- * @example
6005
- * ```typescript
6006
- * const result = await relyio.adhoc()
6007
- * .from('product')
6008
- * .where({ status: 'active' })
6009
- * .include(['brand', 'categories'])
6010
- * .select({
6011
- * id: 'id',
6012
- * title: 'title',
6013
- * price: 'pricing.basePrice'
6014
- * })
6015
- * .limit(20)
6016
- * .execute()
6017
- * ```
6018
- */
6019
- adhoc() {
6020
- return this.query.build();
6021
- }
6022
- /**
6023
- * Execute a predefined query by name (from schema)
6024
- * Type-safe when schema is provided: autocompletes query names
6025
- *
6026
- * @example
6027
- * ```typescript
6028
- * const featured = await relyio.executeQuery('featured-products', {
6029
- * limit: 10,
6030
- * category: 'electronics'
6031
- * })
6032
- * ```
6033
- */
6034
- async executeQuery(queryName, params) {
6035
- const clientLocale = this.config.locale;
6036
- const namesLocale = params?.locale != null || params?.locales != null;
6037
- const response = await this.httpClient.request({
6038
- method: "GET",
6039
- url: `/${API_VERSION_SEGMENT}/${String(queryName)}`,
6040
- params: namesLocale || clientLocale === void 0 || clientLocale === "" ? params ?? {} : { ...params, locale: clientLocale }
6041
- });
6042
- const payload = readPayload(response.body);
6043
- return Array.isArray(payload) ? payload : [payload];
6044
- }
6045
4312
  /**
6046
4313
  * Describe the project this key resolves to — `GET /v1/project`.
6047
4314
  *
@@ -6060,46 +4327,61 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
6060
4327
  return this.httpClient.fetchPayload("/v1/project", "get", {});
6061
4328
  }
6062
4329
  /**
6063
- * Get schema information from the API
4330
+ * The schema definition the key's branch has deployed, as stored — `GET /v1/meta/schema` — or,
4331
+ * given a content type or singleton id, that one type's definition — `GET /v1/meta/schema/{id}`.
4332
+ *
4333
+ * It answers what the deployed schema declares; `project()` answers where the key points
4334
+ * (organization, project, dataset, branch, Studio link). The two share no field.
4335
+ *
4336
+ * @example
4337
+ * ```typescript
4338
+ * const definition = await relyio.schema('post')
4339
+ * ```
6064
4340
  */
6065
- async getSchemaInfo(contentType) {
4341
+ async schema(contentType) {
6066
4342
  return contentType == null ? this.httpClient.fetchPayload("/v1/meta/schema", "get", {}) : this.httpClient.fetchPayload("/v1/meta/schema/{contentType}", "get", {
6067
4343
  path: { contentType }
6068
4344
  });
6069
4345
  }
6070
4346
  /**
6071
- * List all content types from the API
4347
+ * The content types and singletons the key's branch has deployed — `GET /v1/meta/content-types`.
4348
+ *
4349
+ * Each is named by its `id` (what `content()` and `singleton()` take), its `name` (the schema's
4350
+ * `displayName`) and its `type`, content types first, each group in the order the schema declares it.
4351
+ *
4352
+ * @example
4353
+ * ```typescript
4354
+ * const types = await relyio.contentTypes()
4355
+ * const singletons = types.filter(type => type.type === 'singleton').map(type => type.id)
4356
+ * ```
6072
4357
  */
6073
- async listContentTypes() {
6074
- return this.httpClient.fetchPayload("/v1/meta/content-types", "get", {});
4358
+ async contentTypes() {
4359
+ return this.httpClient.fetchPayload(
4360
+ "/v1/meta/content-types",
4361
+ "get",
4362
+ {}
4363
+ );
6075
4364
  }
6076
4365
  /**
6077
- * Get API health status
4366
+ * Whether the API can serve requests now — `GET /health/ready`.
4367
+ *
4368
+ * Resolves `{ status: 'ok', timestamp }` when the API answers and reaches every store it reads.
4369
+ * Rejects otherwise, with the error the request met: a `503` naming the store that did not answer,
4370
+ * or a network error when the API could not be reached at all. So there is no `status` to check:
4371
+ * a call that resolves is the healthy answer.
4372
+ *
4373
+ * @example
4374
+ * ```typescript
4375
+ * try {
4376
+ * await relyio.health()
4377
+ * } catch (error) {
4378
+ * console.error('RelyIO cannot serve now', error)
4379
+ * }
4380
+ * ```
6078
4381
  */
6079
4382
  async health() {
6080
- return this.httpClient.fetchPayload("/health", "get", {});
6081
- }
6082
- /**
6083
- * Check if the API is ready
6084
- */
6085
- async ready() {
6086
- try {
6087
- const payload = await this.httpClient.fetchPayload("/health/ready", "get", {});
6088
- return payload.status === "ok";
6089
- } catch {
6090
- return false;
6091
- }
6092
- }
6093
- /**
6094
- * Test the connection to the API
6095
- */
6096
- async testConnection() {
6097
- try {
6098
- await this.health();
6099
- return true;
6100
- } catch {
6101
- return false;
6102
- }
4383
+ const { status, timestamp } = await this.httpClient.fetchPayload("/health/ready", "get", {});
4384
+ return { status, timestamp };
6103
4385
  }
6104
4386
  /**
6105
4387
  * Change some of the client's options and keep the rest.
@@ -6127,11 +4409,10 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
6127
4409
  *
6128
4410
  * The client's one HTTP client is changed in place, so every request from now on is sent as the
6129
4411
  * update says — including one from a `content()`, `asset()`, `singleton()` or `settings()`
6130
- * sub-client, a `query` client, or a builder such as `search()` or `batch()`, taken before the
6131
- * call. The locale reaches them too: each reads the client's locale when it sends, so a new
6132
- * `locale` is the default of its next request and `locale: null` leaves that request with none. A
6133
- * locale the call names, and a sub-client's own `withLocale()`, still win over it. A client made
6134
- * by `withLocale()` is a client of its own: an update of this one does not reach it.
4412
+ * sub-client taken before the call. The locale reaches them too: each reads the client's locale
4413
+ * when it sends, so a new `locale` is the default of its next request and `locale: null` leaves
4414
+ * that request with none. A locale the call names still wins over it, and so does the locale of a
4415
+ * client `with({ locale })` made from this one.
6135
4416
  *
6136
4417
  * @example
6137
4418
  * ```typescript
@@ -6144,6 +4425,7 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
6144
4425
  * ```
6145
4426
  */
6146
4427
  updateConfig(update) {
4428
+ if (this.parent !== void 0) return this.parent.updateConfig(update);
6147
4429
  const resolved = resolveConfigUpdate(this.configured, update);
6148
4430
  this.config = resolved.config;
6149
4431
  this.configured = resolved.configured;
@@ -6154,7 +4436,8 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
6154
4436
  * Get the current configuration
6155
4437
  */
6156
4438
  getConfig() {
6157
- return { ...this.config };
4439
+ if (this.parent === void 0) return { ...this.config };
4440
+ return { ...this.parent.getConfig(), ...this.defaults };
6158
4441
  }
6159
4442
  /**
6160
4443
  * Replace the API key every later request sends as `X-API-Key`.
@@ -6162,8 +4445,8 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
6162
4445
  * It reaches every request this client makes from now on, including those of a `content()`,
6163
4446
  * `asset()` or other sub-client taken before the call, and `getConfig().apiKey` reports it. A key
6164
4447
  * given in `headers`, under any spelling, is replaced with it and does not come back on a later
6165
- * `updateConfig()`. A client already derived with `withLocale()` is a client of its own and keeps
6166
- * its key.
4448
+ * `updateConfig()`. A client `with()` made from this one sends the new key too: the two share one
4449
+ * connection.
6167
4450
  *
6168
4451
  * An empty or blank key is no key, as it is when passed to `createClient`: the next request
6169
4452
  * rejects with `MissingApiKeyError` and nothing is sent.
@@ -6179,6 +4462,7 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
6179
4462
  * ```
6180
4463
  */
6181
4464
  setApiKey(apiKey) {
4465
+ if (this.parent !== void 0) return this.parent.setApiKey(apiKey);
6182
4466
  this.configured = withConfiguredKey(this.configured, apiKey);
6183
4467
  if (this._sandbox.state === "active") return;
6184
4468
  this.httpClient.setApiKey(apiKey);
@@ -6193,73 +4477,76 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
6193
4477
  // LOCALE OPERATIONS
6194
4478
  // ============================================================================
6195
4479
  /**
6196
- * Create a locale-scoped client
6197
- *
6198
- * Every call the returned client builds is in this locale unless the call names one of its own,
6199
- * which wins: every read — `content()`, `asset()`, `singleton()`, `settings()`, `search()`,
6200
- * `similar()`, `autocomplete()`, `executeQuery()` and the ad-hoc queries — and every write and
6201
- * version-history read — `create()` and `update()`, the singleton and settings writes, the asset
6202
- * file writes, `versions()` and `version()`, and each operation `batch()` builds. `restore()`
6203
- * always names its locale; a delete, and a `batch().raw()` operation, carry none of the client's.
4480
+ * Get the current default locale
4481
+ */
4482
+ getLocale() {
4483
+ return this.currentLocale();
4484
+ }
4485
+ /**
4486
+ * A client whose reads and writes are in `locale` and `dataset` unless a call names its own, and
4487
+ * which otherwise **follows this one** (REL-2417, Q9): it sends this client's key, to this
4488
+ * client's base URL, under this client's configuration as it is when each request is sent — so a
4489
+ * later `setApiKey()` or `updateConfig()` here reaches it. A default it does not name is this
4490
+ * client's, as this client changes it.
6204
4491
  *
6205
- * Type-safe when schema is provided: autocompletes valid locale codes!
4492
+ * The two share one connection, so a `setApiKey()` or `updateConfig()` called on the client made
4493
+ * here changes this one too. A call's own `locale` or `dataset` wins over the defaults.
6206
4494
  *
6207
4495
  * @example
6208
4496
  * ```typescript
6209
- * const spanish = relyio.withLocale('es-ES') // ← Autocompletes with schema!
6210
- *
6211
- * // Reads and writes use Spanish
6212
- * const products = await spanish.content('product').query().execute()
6213
- * const homepage = await spanish.singleton('homepage').fetch()
6214
- * const results = await spanish.search('zapatos').execute()
6215
- * await spanish.content('product').update(id, { title: 'Zapatos' })
4497
+ * const staging = relyio.with({ dataset: 'staging' })
4498
+ * const { data } = await staging.content('post').list({ limit: 10 })
6216
4499
  *
6217
- * // Original client unchanged
6218
- * const englishProducts = await relyio.content('product').query().execute()
4500
+ * const french = relyio.with({ locale: 'fr-FR' }) // ← autocompletes the schema's locales
6219
4501
  * ```
6220
4502
  */
6221
- withLocale(locale) {
6222
- const newConfig = {
6223
- ...this.configured,
6224
- locale,
4503
+ with(defaults) {
4504
+ const child = new _TypedRelyIOClient({
4505
+ ...this.configuredNow(),
4506
+ dev: false,
6225
4507
  schema: this._schema
4508
+ });
4509
+ child.follow(this, defaults);
4510
+ return child;
4511
+ }
4512
+ /** Make this client one `with()` made from `parent`: see {@link with}. */
4513
+ follow(parent, defaults) {
4514
+ const { locale, dataset } = defaults;
4515
+ this.parent = parent;
4516
+ this.defaults = {
4517
+ ...locale === void 0 ? {} : { locale },
4518
+ ...dataset === void 0 ? {} : { dataset }
6226
4519
  };
6227
- return new _TypedRelyIOClient(newConfig);
4520
+ this.httpClient = parent.httpClient.withHeaders(
4521
+ dataset === void 0 ? {} : { [DATASET_HEADER]: dataset }
4522
+ );
6228
4523
  }
6229
4524
  /**
6230
- * Get the current default locale
4525
+ * The configuration as given, before the `relyio dev` handshake — this client's own, or for a
4526
+ * client `with()` made, its parent's with its defaults laid over it.
6231
4527
  */
6232
- getLocale() {
6233
- return this.config.locale;
4528
+ configuredNow() {
4529
+ return this.parent === void 0 ? this.configured : { ...this.parent.configuredNow(), ...this.defaults };
6234
4530
  }
6235
4531
  /**
6236
- * Get available locales from the API
4532
+ * Every locale the key's branch has deployed, the default marked — `GET /v1/meta/locales`.
4533
+ *
4534
+ * In the order the schema declares them. Exactly one has `default: true`, the locale a read or
4535
+ * write that names none is in; every other has `default: false`. `fallback`, `direction` and
4536
+ * `active` are as the schema declares them, absent where it does not.
6237
4537
  *
6238
4538
  * @example
6239
4539
  * ```typescript
6240
- * const { locales, defaultLocale } = await relyio.getLocales()
4540
+ * const locales = await relyio.locales()
4541
+ * const defaultLocale = locales.find(locale => locale.default)?.code
6241
4542
  *
6242
- * // Build language switcher
6243
- * const options = locales.map(l => ({
6244
- * code: l.code,
6245
- * label: l.displayName,
6246
- * isDefault: l.default,
6247
- * }))
4543
+ * // Build a language switcher
4544
+ * const options = locales.map(locale => ({ code: locale.code, label: locale.displayName }))
6248
4545
  * ```
6249
4546
  */
6250
- async getLocales(search) {
6251
- const params = {};
6252
- if (search !== void 0 && search !== "") {
6253
- params.search = search;
6254
- }
6255
- return this.httpClient.fetchPayload("/v1/meta/locales", "get", { query: params });
6256
- }
6257
- /**
6258
- * Get the default locale from the API
6259
- */
6260
- async getDefaultLocale() {
6261
- const { defaultLocale } = await this.getLocales();
6262
- return defaultLocale;
4547
+ async locales() {
4548
+ const { locales, defaultLocale } = await this.httpClient.fetchPayload("/v1/meta/locales", "get", {});
4549
+ return locales.map((locale) => ({ ...locale, default: locale.code === defaultLocale }));
6263
4550
  }
6264
4551
  };
6265
4552
  __name(_TypedRelyIOClient, "TypedRelyIOClient");
@@ -6282,6 +4569,6 @@ function randomClientId() {
6282
4569
  }
6283
4570
  __name(randomClientId, "randomClientId");
6284
4571
 
6285
- export { AdhocQueryBuilder, AdhocQueryClient, AssetClient, AssetFileBuilder, AssetUploadBuilder, AuthorizationError, AutocompleteBuilder, BaseValidationError, BatchBuilder, BatchClient, ChangesExpiredError, ConnectionError, ContentByIdBuilder, ContentClient, ContentQueryBuilder, ErrorMapper, FileUploadError, FileValidationError, HttpClient, InvalidApiKeyError, InvalidPayloadError, LIVE_KEEPALIVE_INTERVAL_MS, LIVE_SOCKET_OPEN_TIMEOUT_MS, LiveEdgeError, MethodNotAllowedError, MissingApiKeyError, NetworkError, PUBLISHABLE_KEY_PREFIX, PayloadTooLargeError, PlanFeatureUnavailableError, RateLimitError, ReadOnlyKeyError, ResourceConflictError, ResourceExpiredError, ResourceNotFoundError, SDKError, SearchBuilder, SearchClient, ServerError, ServiceIndeterminateError, SettingsClient, SimilarBuilder, SingletonClient, TimeoutError, TransactionRolledBackError, TypedRelyIOClient, UnknownContractError, UsageQuotaExceededError, VISITOR_ADMISSION_RETRY_AFTER_SECONDS, ValidationError, createClient, entityTagOf, isAborted, isAssetEntry, isPublishableKey, isResourceNotFound, matchRoute, serviceInstructedDelayMs, sleep, validateFile };
6286
- //# sourceMappingURL=chunk-6NGJXLOR.js.map
6287
- //# sourceMappingURL=chunk-6NGJXLOR.js.map
4572
+ export { AssetClient, AssetFileBuilder, AssetUploadBuilder, AuthorizationError, BaseValidationError, ChangesExpiredError, ConnectionError, ContentClient, ErrorMapper, FileUploadError, FileValidationError, HttpClient, InvalidApiKeyError, InvalidPayloadError, LIVE_KEEPALIVE_INTERVAL_MS, LIVE_SOCKET_OPEN_TIMEOUT_MS, LiveEdgeError, MethodNotAllowedError, MissingApiKeyError, NetworkError, PUBLISHABLE_KEY_PREFIX, PayloadTooLargeError, PlanFeatureUnavailableError, RateLimitError, ReadOnlyKeyError, ResourceConflictError, ResourceExpiredError, ResourceNotFoundError, SDKError, ServerError, ServiceIndeterminateError, SettingsClient, SingletonClient, TimeoutError, TransactionRolledBackError, TypedRelyIOClient, UnknownContractError, UsageQuotaExceededError, VISITOR_ADMISSION_RETRY_AFTER_SECONDS, ValidationError, createClient, isAborted, isAssetEntry, isMissingEntry, isPublishableKey, isResourceNotFound, matchRoute, serviceInstructedDelayMs, sleep, validateFile, versionCheckOf };
4573
+ //# sourceMappingURL=chunk-DBBZVZOS.js.map
4574
+ //# sourceMappingURL=chunk-DBBZVZOS.js.map