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