@relyio/sdk 0.3.0 → 0.5.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.
@@ -736,6 +736,111 @@ function fillPath(template, params) {
736
736
  return filled;
737
737
  }
738
738
  chunkMWMSFIWN_cjs.__name(fillPath, "fillPath");
739
+ var DECLARED_PATHS = {
740
+ "/health": true,
741
+ "/health/cache": true,
742
+ "/health/live": true,
743
+ "/health/ready": true,
744
+ "/internal/media/claim": true,
745
+ "/internal/media/file-complete": true,
746
+ "/internal/media/progress": true,
747
+ "/internal/media/resolve": true,
748
+ "/internal/usage/cycle": true,
749
+ "/internal/usage/ingest": true,
750
+ "/v1/{contentType}": true,
751
+ "/v1/{contentType}/{id}": true,
752
+ "/v1/{contentType}/{id}/children": true,
753
+ "/v1/{contentType}/{id}/file": true,
754
+ "/v1/{contentType}/{id}/file/confirm": true,
755
+ "/v1/{contentType}/{id}/file/multipart/abort": true,
756
+ "/v1/{contentType}/{id}/file/multipart/complete": true,
757
+ "/v1/{contentType}/{id}/file/multipart/create": true,
758
+ "/v1/{contentType}/{id}/file/multipart/list-parts": true,
759
+ "/v1/{contentType}/{id}/file/multipart/sign-part": true,
760
+ "/v1/{contentType}/{id}/file/presign": true,
761
+ "/v1/{contentType}/{id}/file/url": true,
762
+ "/v1/{contentType}/{id}/versions": true,
763
+ "/v1/{contentType}/{id}/versions/{versionNumber}": true,
764
+ "/v1/{contentType}/{id}/versions/{versionNumber}/restore": true,
765
+ "/v1/batch": true,
766
+ "/v1/changes": true,
767
+ "/v1/meta/content-types": true,
768
+ "/v1/meta/info": true,
769
+ "/v1/meta/locales": true,
770
+ "/v1/meta/schema": true,
771
+ "/v1/meta/schema/{contentType}": true,
772
+ "/v1/meta/schema/{contentType}/fields": true,
773
+ "/v1/project": true,
774
+ "/v1/query": true,
775
+ "/v1/search": true,
776
+ "/v1/search/autocomplete": true,
777
+ "/v1/search/similar": true,
778
+ "/v1/settings": true,
779
+ "/v1/settings/{groupId}": true,
780
+ "/v1/settings/{groupId}/defaults": true,
781
+ "/v1/settings/{groupId}/reset": true,
782
+ "/v1/settings/{groupId}/versions": true,
783
+ "/v1/settings/{groupId}/versions/{versionNumber}": true,
784
+ "/v1/singletons/{key}": true,
785
+ "/v1/singletons/{key}/exists": true,
786
+ "/v1/singletons/{key}/versions": true,
787
+ "/v1/singletons/{key}/versions/{versionNumber}": true
788
+ };
789
+ function compile(path) {
790
+ const segments = path.split("/").slice(1).map((part) => {
791
+ const param = /^\{(\w+)\}$/.exec(part);
792
+ return param?.[1] === void 0 ? { kind: "static", value: part } : { kind: "param", name: param[1] };
793
+ });
794
+ return { path, segments };
795
+ }
796
+ chunkMWMSFIWN_cjs.__name(compile, "compile");
797
+ function byRouterPrecedence(a, b) {
798
+ const shared = Math.min(a.segments.length, b.segments.length);
799
+ for (let index = 0; index < shared; index += 1) {
800
+ const left = a.segments[index]?.kind;
801
+ const right = b.segments[index]?.kind;
802
+ if (left !== right) return left === "static" ? -1 : 1;
803
+ }
804
+ return 0;
805
+ }
806
+ chunkMWMSFIWN_cjs.__name(byRouterPrecedence, "byRouterPrecedence");
807
+ var COMPILED_ROUTES = Object.keys(DECLARED_PATHS).map(compile).sort(byRouterPrecedence);
808
+ function matchRoute(url, options = {}) {
809
+ const parsed = new URL(url, "http://relative.invalid");
810
+ let pathname = parsed.pathname;
811
+ if (options.baseUrl !== void 0) {
812
+ const prefix = new URL(options.baseUrl, "http://relative.invalid").pathname.replace(/\/+$/, "");
813
+ if (prefix !== "") {
814
+ if (pathname !== prefix && !pathname.startsWith(`${prefix}/`)) return null;
815
+ pathname = pathname.slice(prefix.length);
816
+ }
817
+ }
818
+ const parts = pathname.split("/").slice(1);
819
+ for (const route of COMPILED_ROUTES) {
820
+ if (route.segments.length !== parts.length) continue;
821
+ const params = {};
822
+ const matched = route.segments.every((segment, index) => {
823
+ const part = parts[index];
824
+ if (part === void 0) return false;
825
+ if (segment.kind === "static") return segment.value === part;
826
+ if (part === "") return false;
827
+ params[segment.name] = decodeURIComponent(part);
828
+ return true;
829
+ });
830
+ if (matched) return { path: route.path, params, query: readQuery(parsed.searchParams) };
831
+ }
832
+ return null;
833
+ }
834
+ chunkMWMSFIWN_cjs.__name(matchRoute, "matchRoute");
835
+ function readQuery(searchParams) {
836
+ const query = {};
837
+ for (const key of new Set(searchParams.keys())) {
838
+ const values = searchParams.getAll(key);
839
+ query[key] = values.length === 1 && values[0] !== void 0 ? values[0] : values;
840
+ }
841
+ return query;
842
+ }
843
+ chunkMWMSFIWN_cjs.__name(readQuery, "readQuery");
739
844
 
740
845
  // src/http/retry-policy.ts
741
846
  var IDEMPOTENT_METHODS = /* @__PURE__ */ new Set([
@@ -883,6 +988,7 @@ var _HttpClient = class _HttpClient {
883
988
  return response;
884
989
  } catch (error) {
885
990
  const failure = error;
991
+ if (config.signal?.aborted === true) throw failure;
886
992
  this.log("warn", "Request failed", {
887
993
  error: error instanceof Error ? error.message : "Unknown error",
888
994
  attempt,
@@ -891,7 +997,7 @@ var _HttpClient = class _HttpClient {
891
997
  if (this.retryPolicy.shouldRetry(attempt, failure, mergedConfig)) {
892
998
  const delay = this.retryPolicy.delayFor(attempt, failure);
893
999
  this.log("debug", "Retrying request", { attempt, delay });
894
- await this.sleep(delay);
1000
+ await this.sleep(delay, config.signal);
895
1001
  continue;
896
1002
  }
897
1003
  const sdkError = ErrorMapper.mapGenericError(failure, {
@@ -990,20 +1096,41 @@ var _HttpClient = class _HttpClient {
990
1096
  getBaseUrl() {
991
1097
  return this.baseUrl;
992
1098
  }
1099
+ /**
1100
+ * One attempt.
1101
+ *
1102
+ * The caller's `signal` and the timeout both end it, through one controller.
1103
+ * The incumbent handed `fetch` the caller's signal **instead of** its own, so a
1104
+ * request that passed a signal had no timeout at all — and a caller abort then
1105
+ * surfaced as `Request timed out`. `timedOut` is what tells the two apart: a
1106
+ * timeout is a `TimeoutError`, a caller abort is the platform's `AbortError`,
1107
+ * rethrown as it came.
1108
+ */
993
1109
  async makeRequest(url, config) {
1110
+ const caller = config.signal;
1111
+ if (caller?.aborted === true) {
1112
+ throw new DOMException("The request was aborted before it was sent", "AbortError");
1113
+ }
994
1114
  const controller = new AbortController();
995
- const timeoutId = setTimeout(() => controller.abort(), config.timeout ?? this.timeout);
1115
+ let timedOut = false;
1116
+ const timeoutId = setTimeout(() => {
1117
+ timedOut = true;
1118
+ controller.abort();
1119
+ }, config.timeout ?? this.timeout);
1120
+ const onCallerAbort = /* @__PURE__ */ chunkMWMSFIWN_cjs.__name(() => controller.abort(), "onCallerAbort");
1121
+ caller?.addEventListener("abort", onCallerAbort, { once: true });
996
1122
  try {
997
1123
  const filteredHeaders = this.filterHeadersForBrowser(config.headers ?? {});
998
1124
  const requestInit = {
999
1125
  method: config.method,
1000
1126
  headers: filteredHeaders,
1001
1127
  body: this.prepareRequestBody(config),
1002
- signal: config.signal ?? controller.signal,
1128
+ signal: controller.signal,
1003
1129
  // Include credentials for CORS requests if in browser and configured
1004
1130
  ...typeof window !== "undefined" && this.config.browser.includeCredentials != null && { credentials: "include" }
1005
1131
  };
1006
- const response = await fetch(url, requestInit);
1132
+ const send = this.config.fetch ?? globalThis.fetch;
1133
+ const response = await send(url, requestInit);
1007
1134
  clearTimeout(timeoutId);
1008
1135
  const responseBody = await this.parseResponse(response);
1009
1136
  if (!response.ok) {
@@ -1034,6 +1161,7 @@ var _HttpClient = class _HttpClient {
1034
1161
  throw error;
1035
1162
  }
1036
1163
  if (error instanceof DOMException && error.name === "AbortError") {
1164
+ if (!timedOut && isAborted(caller)) throw error;
1037
1165
  throw new TimeoutError(
1038
1166
  `Request timed out after ${config.timeout ?? this.timeout}ms`,
1039
1167
  config.timeout ?? this.timeout
@@ -1065,6 +1193,8 @@ var _HttpClient = class _HttpClient {
1065
1193
  throw corsError;
1066
1194
  }
1067
1195
  throw error;
1196
+ } finally {
1197
+ caller?.removeEventListener("abort", onCallerAbort);
1068
1198
  }
1069
1199
  }
1070
1200
  /**
@@ -1153,8 +1283,21 @@ var _HttpClient = class _HttpClient {
1153
1283
  });
1154
1284
  return obj;
1155
1285
  }
1156
- sleep(ms) {
1157
- return new Promise((resolve) => setTimeout(resolve, ms));
1286
+ /** A backoff the caller's abort cuts short; the next attempt then refuses to send. */
1287
+ sleep(ms, signal) {
1288
+ return new Promise((resolve) => {
1289
+ if (isAborted(signal)) {
1290
+ resolve();
1291
+ return;
1292
+ }
1293
+ const done = /* @__PURE__ */ chunkMWMSFIWN_cjs.__name(() => {
1294
+ clearTimeout(timer);
1295
+ signal?.removeEventListener("abort", done);
1296
+ resolve();
1297
+ }, "done");
1298
+ const timer = setTimeout(done, ms);
1299
+ signal?.addEventListener("abort", done, { once: true });
1300
+ });
1158
1301
  }
1159
1302
  log(level, message, data) {
1160
1303
  if (!this.debug) return;
@@ -1167,6 +1310,10 @@ var _HttpClient = class _HttpClient {
1167
1310
  };
1168
1311
  chunkMWMSFIWN_cjs.__name(_HttpClient, "HttpClient");
1169
1312
  var HttpClient = _HttpClient;
1313
+ function isAborted(signal) {
1314
+ return signal?.aborted === true;
1315
+ }
1316
+ chunkMWMSFIWN_cjs.__name(isAborted, "isAborted");
1170
1317
 
1171
1318
  // src/content/query-builder.ts
1172
1319
  var _ContentQueryBuilder = class _ContentQueryBuilder {
@@ -3934,7 +4081,108 @@ var _AdhocQueryClient = class _AdhocQueryClient {
3934
4081
  chunkMWMSFIWN_cjs.__name(_AdhocQueryClient, "AdhocQueryClient");
3935
4082
  var AdhocQueryClient = _AdhocQueryClient;
3936
4083
 
4084
+ // src/changes/changes.ts
4085
+ var CHANGES_POLL_TIMEOUT_MS = 25e3 + 5e3;
4086
+ var _ChangesExpiredError = class _ChangesExpiredError extends ResourceExpiredError {
4087
+ /** The base class's signature, so `withContext` can rebuild one. */
4088
+ constructor(message, statusCode = 410, cause, context) {
4089
+ super(message, statusCode, cause, context);
4090
+ chunkMWMSFIWN_cjs.__publicField(this, "reason");
4091
+ this.name = "ChangesExpiredError";
4092
+ this.reason = expiredReasonOf(message);
4093
+ }
4094
+ static of(expired) {
4095
+ return new _ChangesExpiredError(expired.message, expired.statusCode, expired, expired.context);
4096
+ }
4097
+ };
4098
+ chunkMWMSFIWN_cjs.__name(_ChangesExpiredError, "ChangesExpiredError");
4099
+ var ChangesExpiredError = _ChangesExpiredError;
4100
+ function expiredReasonOf(message) {
4101
+ if (message.includes("(retention)")) return "retention";
4102
+ if (message.includes("(dataset-replaced)")) return "dataset-replaced";
4103
+ return "unknown";
4104
+ }
4105
+ chunkMWMSFIWN_cjs.__name(expiredReasonOf, "expiredReasonOf");
4106
+ async function* pollChanges(http, options = {}) {
4107
+ const { signal } = options;
4108
+ let since = options.since;
4109
+ let resynced;
4110
+ for (; ; ) {
4111
+ if (aborted(signal)) return;
4112
+ let response;
4113
+ try {
4114
+ response = await pollOnce(http, since, options);
4115
+ } catch (error) {
4116
+ if (aborted(signal)) return;
4117
+ if (!(error instanceof ResourceExpiredError) || since === void 0) throw error;
4118
+ const expired = ChangesExpiredError.of(error);
4119
+ const onExpired = options.onExpired ?? "throw";
4120
+ if (onExpired === "throw") throw expired;
4121
+ since = void 0;
4122
+ resynced = typeof onExpired === "function" ? expired : void 0;
4123
+ continue;
4124
+ }
4125
+ const { data, page } = response.body;
4126
+ if (page.nextCursor === null) {
4127
+ throw new Error("GET /v1/changes answered without page.nextCursor");
4128
+ }
4129
+ since = page.nextCursor;
4130
+ if (resynced !== void 0 && typeof options.onExpired === "function") {
4131
+ const expired = resynced;
4132
+ resynced = void 0;
4133
+ await options.onExpired(expired);
4134
+ if (aborted(signal)) return;
4135
+ }
4136
+ for (const change of data) {
4137
+ yield change;
4138
+ if (aborted(signal)) return;
4139
+ }
4140
+ if (data.length === 0) {
4141
+ const wait = parseRetryAfterMs(headerValue(response.headers, "Retry-After"), Date.now());
4142
+ if (wait !== void 0 && wait > 0) await sleep(wait, signal);
4143
+ }
4144
+ }
4145
+ }
4146
+ chunkMWMSFIWN_cjs.__name(pollChanges, "pollChanges");
4147
+ async function pollOnce(http, since, options) {
4148
+ const query = {};
4149
+ if (since !== void 0) query.since = since;
4150
+ if (options.types !== void 0) query.types = options.types.join(",");
4151
+ if (options.ids !== void 0) query.ids = options.ids.join(",");
4152
+ if (options.limit !== void 0) query.limit = options.limit;
4153
+ return http.send("/v1/changes", "get", {
4154
+ query,
4155
+ timeout: CHANGES_POLL_TIMEOUT_MS,
4156
+ ...options.signal === void 0 ? {} : { signal: options.signal }
4157
+ });
4158
+ }
4159
+ chunkMWMSFIWN_cjs.__name(pollOnce, "pollOnce");
4160
+ function sleep(ms, signal) {
4161
+ return new Promise((resolve) => {
4162
+ if (aborted(signal)) {
4163
+ resolve();
4164
+ return;
4165
+ }
4166
+ const done = /* @__PURE__ */ chunkMWMSFIWN_cjs.__name(() => {
4167
+ clearTimeout(timer);
4168
+ signal?.removeEventListener("abort", done);
4169
+ resolve();
4170
+ }, "done");
4171
+ const timer = setTimeout(done, ms);
4172
+ signal?.addEventListener("abort", done, { once: true });
4173
+ });
4174
+ }
4175
+ chunkMWMSFIWN_cjs.__name(sleep, "sleep");
4176
+ function aborted(signal) {
4177
+ return signal?.aborted === true;
4178
+ }
4179
+ chunkMWMSFIWN_cjs.__name(aborted, "aborted");
4180
+
3937
4181
  // src/config/dev-detection.ts
4182
+ function sandboxStateOf(resolution) {
4183
+ return resolution.state === "active" ? resolution.sandbox : resolution;
4184
+ }
4185
+ chunkMWMSFIWN_cjs.__name(sandboxStateOf, "sandboxStateOf");
3938
4186
  function getProcess() {
3939
4187
  return typeof process !== "undefined" ? process : void 0;
3940
4188
  }
@@ -3958,11 +4206,14 @@ function resolveFromEnv(proc, now) {
3958
4206
  case "active":
3959
4207
  return {
3960
4208
  state: "active",
3961
- override: {
3962
- dataset: verdict.credentials.dataset,
3963
- apiKey: verdict.credentials.apiKey,
4209
+ sandbox: {
4210
+ state: "active",
4211
+ datasetId: verdict.credentials.dataset,
4212
+ branchId: null,
4213
+ expiresAt: verdict.expiresAt,
3964
4214
  source: "env"
3965
- }
4215
+ },
4216
+ apiKey: verdict.credentials.apiKey
3966
4217
  };
3967
4218
  case "expired":
3968
4219
  return { state: "expired", expiredAt: verdict.expiredAt };
@@ -3993,11 +4244,14 @@ function resolveFromFile(proc, now) {
3993
4244
  case "active":
3994
4245
  return {
3995
4246
  state: "active",
3996
- override: {
3997
- dataset: verdict.context.datasetId,
3998
- apiKey: verdict.context.previewApiKey,
4247
+ sandbox: {
4248
+ state: "active",
4249
+ datasetId: verdict.context.datasetId,
4250
+ branchId: verdict.context.branchId,
4251
+ expiresAt: verdict.context.expiresAt,
3999
4252
  source: "file"
4000
- }
4253
+ },
4254
+ apiKey: verdict.context.previewApiKey
4001
4255
  };
4002
4256
  case "expired":
4003
4257
  return { state: "expired", expiredAt: verdict.expiredAt };
@@ -4078,7 +4332,7 @@ function validateConfig(config) {
4078
4332
  }
4079
4333
  chunkMWMSFIWN_cjs.__name(validateConfig, "validateConfig");
4080
4334
  var devOverrideNotified = false;
4081
- function mergeConfig(userConfig) {
4335
+ function resolveConfig(userConfig) {
4082
4336
  validateConfig(userConfig);
4083
4337
  const merged = {
4084
4338
  ...DEFAULT_CONFIG,
@@ -4106,12 +4360,12 @@ function mergeConfig(userConfig) {
4106
4360
  };
4107
4361
  const dev = resolveDevOverride(userConfig.dev);
4108
4362
  if (dev.state === "active") {
4109
- merged.apiKey = dev.override.apiKey;
4110
- merged.dataset = dev.override.dataset;
4363
+ merged.apiKey = dev.apiKey;
4364
+ merged.dataset = dev.sandbox.datasetId;
4111
4365
  if (!devOverrideNotified) {
4112
4366
  devOverrideNotified = true;
4113
4367
  console.info(
4114
- `[RelyIO] Sandbox dev mode active (${dev.override.source}) \u2014 targeting dataset "${dev.override.dataset}" with a scoped preview key.`
4368
+ `[RelyIO] Sandbox dev mode active (${dev.sandbox.source}) \u2014 targeting dataset "${dev.sandbox.datasetId}" with a scoped preview key.`
4115
4369
  );
4116
4370
  }
4117
4371
  } else if (dev.state === "expired" && !devOverrideNotified) {
@@ -4125,9 +4379,9 @@ function mergeConfig(userConfig) {
4125
4379
  `[RelyIO] Your .relyio/dev.json could not be read (${dev.reason}) \u2014 ignoring it and using the configured dataset "${merged.dataset}". Run \`relyio dev\` to rewrite it.`
4126
4380
  );
4127
4381
  }
4128
- return merged;
4382
+ return { config: merged, sandbox: sandboxStateOf(dev) };
4129
4383
  }
4130
- chunkMWMSFIWN_cjs.__name(mergeConfig, "mergeConfig");
4384
+ chunkMWMSFIWN_cjs.__name(resolveConfig, "resolveConfig");
4131
4385
 
4132
4386
  // src/client/create-client.ts
4133
4387
  var API_VERSION_SEGMENT = "v1";
@@ -4135,13 +4389,16 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
4135
4389
  constructor(config) {
4136
4390
  chunkMWMSFIWN_cjs.__publicField(this, "httpClient");
4137
4391
  chunkMWMSFIWN_cjs.__publicField(this, "config");
4392
+ chunkMWMSFIWN_cjs.__publicField(this, "_sandbox");
4138
4393
  chunkMWMSFIWN_cjs.__publicField(this, "_search");
4139
4394
  chunkMWMSFIWN_cjs.__publicField(this, "_batch");
4140
4395
  chunkMWMSFIWN_cjs.__publicField(this, "_adhocQuery");
4141
4396
  chunkMWMSFIWN_cjs.__publicField(this, "_schema");
4142
4397
  const { schema, ...clientConfig } = config;
4143
4398
  this._schema = schema;
4144
- this.config = mergeConfig(clientConfig);
4399
+ const resolved = resolveConfig(clientConfig);
4400
+ this.config = resolved.config;
4401
+ this._sandbox = resolved.sandbox;
4145
4402
  this.httpClient = new HttpClient(this.config);
4146
4403
  this._search = new SearchClient(this.httpClient);
4147
4404
  this._batch = new BatchClient(this.httpClient);
@@ -4156,6 +4413,23 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
4156
4413
  get schema() {
4157
4414
  return this._schema;
4158
4415
  }
4416
+ /**
4417
+ * The `relyio dev` sandbox this client resolved at construction, as the preview toolbar displays
4418
+ * it (`sdk-surfaces-spec.md` decision 22).
4419
+ *
4420
+ * Plain, JSON-serializable data and no key, so a server component can hand it to a client
4421
+ * component as a prop — the browser cannot read the handshake itself, so a client created there
4422
+ * reports `absent`. See {@link SandboxState} for what each state means.
4423
+ *
4424
+ * @example
4425
+ * ```tsx
4426
+ * // app/layout.tsx — a server component handing the state to a client component
4427
+ * <SandboxBadge sandbox={relyio.sandbox} />
4428
+ * ```
4429
+ */
4430
+ get sandbox() {
4431
+ return this._sandbox;
4432
+ }
4159
4433
  /**
4160
4434
  * Get the ad-hoc query client
4161
4435
  */
@@ -4304,6 +4578,37 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
4304
4578
  return this._batch.create();
4305
4579
  }
4306
4580
  // ============================================================================
4581
+ // CHANGES FEED
4582
+ // ============================================================================
4583
+ /**
4584
+ * Follow the dataset's changes feed: each write this key can see, as ids, in
4585
+ * order, long-polled from `GET /v1/changes`.
4586
+ *
4587
+ * Lazy — nothing is requested until the first `next()`. It runs until
4588
+ * `signal` aborts, which ends the `for await` cleanly rather than throwing.
4589
+ * A change names what changed; refetch it through the ordinary read path
4590
+ * (`INV-SS-CHANGE-IS-IDS`).
4591
+ *
4592
+ * @example
4593
+ * ```typescript
4594
+ * const controller = new AbortController()
4595
+ * for await (const change of relyio.changes({ since: cursor, types: ['post'], signal: controller.signal })) {
4596
+ * // { entryId, contentType, locale, kind, cursor }
4597
+ * }
4598
+ * ```
4599
+ *
4600
+ * @example Resync when the cursor has expired
4601
+ * ```typescript
4602
+ * for await (const change of relyio.changes({
4603
+ * since: cursor,
4604
+ * onExpired: async () => invalidateEverything(),
4605
+ * })) { … }
4606
+ * ```
4607
+ */
4608
+ changes(options = {}) {
4609
+ return pollChanges(this.httpClient, options);
4610
+ }
4611
+ // ============================================================================
4307
4612
  // QUERY OPERATIONS
4308
4613
  // ============================================================================
4309
4614
  /**
@@ -4348,6 +4653,23 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
4348
4653
  const payload = readPayload(response.body);
4349
4654
  return Array.isArray(payload) ? payload : [payload];
4350
4655
  }
4656
+ /**
4657
+ * Describe the project this key resolves to — `GET /v1/project`.
4658
+ *
4659
+ * `dataset` is the one this client's requests select (`dataset`, a sandbox from the `relyio dev`
4660
+ * handshake, or the key's default), which is not always the project's default, so a Studio link
4661
+ * built from `studioUrl` should carry it as `?dataset=`. `studioUrl` is `null` — a value, not an
4662
+ * error — when the service has no Dashboard address configured.
4663
+ *
4664
+ * @example
4665
+ * ```typescript
4666
+ * const { studioUrl, dataset } = await relyio.project()
4667
+ * if (studioUrl !== null) window.open(`${studioUrl}?dataset=${dataset.id}`)
4668
+ * ```
4669
+ */
4670
+ async project() {
4671
+ return this.httpClient.fetchPayload("/v1/project", "get", {});
4672
+ }
4351
4673
  /**
4352
4674
  * Get schema information from the API
4353
4675
  */
@@ -4394,7 +4716,9 @@ var _TypedRelyIOClient = class _TypedRelyIOClient {
4394
4716
  * Update the configuration and reinitialize all clients
4395
4717
  */
4396
4718
  updateConfig(newConfig) {
4397
- this.config = mergeConfig({ ...this.config, ...newConfig });
4719
+ const resolved = resolveConfig({ ...this.config, ...newConfig });
4720
+ this.config = resolved.config;
4721
+ this._sandbox = resolved.sandbox;
4398
4722
  this.httpClient = new HttpClient(this.config);
4399
4723
  this._search = new SearchClient(this.httpClient);
4400
4724
  this._batch = new BatchClient(this.httpClient);
@@ -4502,6 +4826,7 @@ exports.AutocompleteBuilder = AutocompleteBuilder;
4502
4826
  exports.BaseValidationError = BaseValidationError;
4503
4827
  exports.BatchBuilder = BatchBuilder;
4504
4828
  exports.BatchClient = BatchClient;
4829
+ exports.ChangesExpiredError = ChangesExpiredError;
4505
4830
  exports.ConnectionError = ConnectionError;
4506
4831
  exports.ContentByIdBuilder = ContentByIdBuilder;
4507
4832
  exports.ContentClient = ContentClient;
@@ -4543,6 +4868,7 @@ exports.createClient = createClient;
4543
4868
  exports.entityTagOf = entityTagOf;
4544
4869
  exports.isAssetEntry = isAssetEntry;
4545
4870
  exports.isResourceNotFound = isResourceNotFound;
4871
+ exports.matchRoute = matchRoute;
4546
4872
  exports.validateFile = validateFile;
4547
- //# sourceMappingURL=chunk-54WP2PWG.cjs.map
4548
- //# sourceMappingURL=chunk-54WP2PWG.cjs.map
4873
+ //# sourceMappingURL=chunk-CIRT6TVT.cjs.map
4874
+ //# sourceMappingURL=chunk-CIRT6TVT.cjs.map
@@ -707,5 +707,5 @@ exports.isLocaleDefinition = isLocaleDefinition;
707
707
  exports.isRoleDefinition = isRoleDefinition;
708
708
  exports.isWorkflowDefinition = isWorkflowDefinition;
709
709
  exports.isWorkflowStageDefinition = isWorkflowStageDefinition;
710
- //# sourceMappingURL=chunk-HMVGXUGR.cjs.map
711
- //# sourceMappingURL=chunk-HMVGXUGR.cjs.map
710
+ //# sourceMappingURL=chunk-HAQELDZN.cjs.map
711
+ //# sourceMappingURL=chunk-HAQELDZN.cjs.map
@@ -672,5 +672,5 @@ __name(component, "component");
672
672
  var fields = fields_exports;
673
673
 
674
674
  export { BUILT_IN_ROLES, BUILT_IN_ROLE_ACTIONS, ROLE_ACTIONS, buildFallbackChain, defineAssetType, defineComponent, defineContentType, defineContentTypeWorkflow, defineLocale, defineQuery, defineRole, defineSchema, defineSettingsGroup, defineSingleton, defineSingletonContentType, defineView, defineWorkflow, defineWorkflowStage, fields, fieldset, findDefaultLocale, findWorkflowStage, getActiveLocales, getBuiltInRoleActions, getFirstWorkflowStage, getLocaleCodes, getNextWorkflowStage, getWorkflowStageIds, isBuiltInRole, isFinalWorkflowStage, isLocaleDefinition, isRoleDefinition, isWorkflowDefinition, isWorkflowStageDefinition };
675
- //# sourceMappingURL=chunk-LYRYCIX5.js.map
676
- //# sourceMappingURL=chunk-LYRYCIX5.js.map
675
+ //# sourceMappingURL=chunk-XY5KFEFY.js.map
676
+ //# sourceMappingURL=chunk-XY5KFEFY.js.map
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var chunk54WP2PWG_cjs = require('../chunk-54WP2PWG.cjs');
3
+ var chunkCIRT6TVT_cjs = require('../chunk-CIRT6TVT.cjs');
4
4
  require('../chunk-LSRPMNPT.cjs');
5
5
  require('../chunk-MWMSFIWN.cjs');
6
6
 
@@ -8,11 +8,11 @@ require('../chunk-MWMSFIWN.cjs');
8
8
 
9
9
  Object.defineProperty(exports, "TypedRelyIOClient", {
10
10
  enumerable: true,
11
- get: function () { return chunk54WP2PWG_cjs.TypedRelyIOClient; }
11
+ get: function () { return chunkCIRT6TVT_cjs.TypedRelyIOClient; }
12
12
  });
13
13
  Object.defineProperty(exports, "createClient", {
14
14
  enumerable: true,
15
- get: function () { return chunk54WP2PWG_cjs.createClient; }
15
+ get: function () { return chunkCIRT6TVT_cjs.createClient; }
16
16
  });
17
17
  //# sourceMappingURL=index.cjs.map
18
18
  //# sourceMappingURL=index.cjs.map
@@ -1,4 +1,5 @@
1
- export { C as ClientSchema, a as TypedClientConfig, T as TypedRelyIOClient, c as createClient } from '../index-BDpar3FO.cjs';
2
- import '../index-ulOrcfPa.cjs';
1
+ export { b as ClientSchema, R as Register, d as RegisteredSchema, a as TypedClientConfig, T as TypedRelyIOClient, c as createClient } from '../index-BozPz4n1.cjs';
2
+ import '../index-C5mRBb6t.cjs';
3
3
  import '../define-content-type-CBfnTWSS.cjs';
4
- import '../define-schema-BfXeR6K8.cjs';
4
+ import '../define-schema-2xbHgtK-.cjs';
5
+ import '../dev-handshake/index.cjs';
@@ -1,4 +1,5 @@
1
- export { C as ClientSchema, a as TypedClientConfig, T as TypedRelyIOClient, c as createClient } from '../index-Dx01iwaf.js';
2
- import '../index-DuEhsGdE.js';
1
+ export { b as ClientSchema, R as Register, d as RegisteredSchema, a as TypedClientConfig, T as TypedRelyIOClient, c as createClient } from '../index-Dzo-upNS.js';
2
+ import '../index-mXUx2H99.js';
3
3
  import '../define-content-type-CBfnTWSS.js';
4
- import '../define-schema-BD6HSPnG.js';
4
+ import '../define-schema-DzYXk4HL.js';
5
+ import '../dev-handshake/index.js';
@@ -1,4 +1,4 @@
1
- export { TypedRelyIOClient, createClient } from '../chunk-QIZOBT5F.js';
1
+ export { TypedRelyIOClient, createClient } from '../chunk-5AZUUV5B.js';
2
2
  import '../chunk-BBUB7C2Z.js';
3
3
  import '../chunk-XQ4QHWZE.js';
4
4
  //# sourceMappingURL=index.js.map
@@ -1419,16 +1419,20 @@ type SchemaSingletonNames<T extends AnySchemaDefinition> = keyof T['singletonCon
1419
1419
  */
1420
1420
  type SchemaQueryNames<T extends AnySchemaDefinition> = keyof T['queries'] & string;
1421
1421
  /**
1422
- * Get settings group names from a schema
1422
+ * Get settings group names from a schema.
1423
+ *
1424
+ * `settingsGroups`, `assetTypes` and `locales` are optional members of `SchemaDefinition`, so
1425
+ * indexing one yields `X | undefined`, which never extends the record — every typed client's
1426
+ * `settings()`, `asset()` and `withLocale()` took `never`. The three read through `NonNullable`.
1423
1427
  */
1424
- type SchemaSettingsGroupNames<T extends AnySchemaDefinition> = T['settingsGroups'] extends Record<string, SettingsGroupDefinition> ? keyof T['settingsGroups'] & string : never;
1428
+ type SchemaSettingsGroupNames<T extends AnySchemaDefinition> = NonNullable<T['settingsGroups']> extends Record<string, SettingsGroupDefinition> ? keyof NonNullable<T['settingsGroups']> & string : never;
1425
1429
  /**
1426
1430
  * Get asset type names from a schema
1427
1431
  */
1428
- type SchemaAssetTypeNames<T extends AnySchemaDefinition> = T['assetTypes'] extends Record<string, AssetTypeDefinition> ? keyof T['assetTypes'] & string : never;
1432
+ type SchemaAssetTypeNames<T extends AnySchemaDefinition> = NonNullable<T['assetTypes']> extends Record<string, AssetTypeDefinition> ? keyof NonNullable<T['assetTypes']> & string : never;
1429
1433
  /**
1430
1434
  * Get locale codes from a schema
1431
1435
  */
1432
- type SchemaLocaleCodes<T extends AnySchemaDefinition> = T['locales'] extends readonly LocaleDefinition[] ? T['locales'][number]['code'] : never;
1436
+ type SchemaLocaleCodes<T extends AnySchemaDefinition> = NonNullable<T['locales']> extends readonly LocaleDefinition[] ? NonNullable<T['locales']>[number]['code'] : never;
1433
1437
 
1434
1438
  export { type VideoCaptionsConfig as $, type SettingsGroupConfig as A, type SettingsGroupDefinition as B, type ComponentConfig as C, type SettingsGroupMetadata as D, type InferSettingsGroup as E, defineAssetType as F, type AssetTypeConfig as G, type AssetTypeConfigWithFields as H, type InferSingletonContentType as I, type AssetTypeConfigWithFieldsets as J, type AssetTypeDefinition as K, type LocaleOptions as L, type InferAssetType as M, type ImageKindConfig as N, type FileKindConfig as O, type ImageTransform as P, type ImageFitMode as Q, type ImageOutputFormat as R, type SchemaConfig as S, type TextDirection as T, type ImageUploadConfig as U, type VideoKindConfig as V, type ImageSourceConfig as W, type ImageExifConfig as X, type VariantDefinition as Y, type VideoUploadConfig as Z, type VideoPosterConfig as _, type SchemaDefinition as a, type VideoDownloadConfig as a0, type VideoWatermarkConfig as a1, type FileUploadConfig as a2, type FileScanConfig as a3, defineRole as a4, isRoleDefinition as a5, isBuiltInRole as a6, getBuiltInRoleActions as a7, BUILT_IN_ROLES as a8, ROLE_ACTIONS as a9, type WhereCondition as aA, type WhereValue as aB, type WhereOperator as aC, type SortCondition as aD, type SortDirection as aE, type IncludeDefinition as aF, type SelectMapping as aG, type FilterDefinition as aH, type CachePolicy as aI, type CachePolicyObject as aJ, type IncludeSubQuery as aK, type IncludeSubQueries as aL, type Parameterizable as aM, type QueryParameter as aN, type QueryParameterType as aO, type QueryCardinality as aP, type InferQueryResponse as aQ, type InferQueryParams as aR, BUILT_IN_ROLE_ACTIONS as aa, type BuiltInRole as ab, type RoleAction as ac, type ResourcePermission as ad, type FieldVisibility as ae, type RoleConfig as af, type RoleDefinition as ag, defineView as ah, type ViewScopeConfig as ai, type ContentTypeScopeConfig as aj, type ContentTypesScopeConfig as ak, type EntryTypeScopeConfig as al, type AllScopeConfig as am, type ViewFilterOperator as an, type ViewFilterCondition as ao, type ViewFilterGroup as ap, type ViewFilter as aq, type ViewConfig as ar, type ViewDefinition as as, defineQuery as at, type QueryConfig as au, type QueryDefinition as av, type FetchBlock as aw, type AggregateBlock as ax, type SearchBlock as ay, type SimilarBlock as az, type SchemaContentTypeNames as b, type SchemaSingletonNames as c, defineSchema as d, type SchemaQueryNames as e, type SchemaLocaleCodes as f, type SchemaSettingsGroupNames as g, type SchemaAssetTypeNames as h, defineLocale as i, isLocaleDefinition as j, getLocaleCodes as k, findDefaultLocale as l, getActiveLocales as m, buildFallbackChain as n, type LocaleDefinition as o, defineSingletonContentType as p, defineSingleton as q, type SingletonContentTypeDefinition as r, type SingletonMetadata as s, type InferSingleton as t, defineComponent as u, type ComponentDefinition as v, type ComponentMetadata as w, type ComponentReusabilityMetadata as x, type ComponentValidationMetadata as y, defineSettingsGroup as z };
@@ -1419,16 +1419,20 @@ type SchemaSingletonNames<T extends AnySchemaDefinition> = keyof T['singletonCon
1419
1419
  */
1420
1420
  type SchemaQueryNames<T extends AnySchemaDefinition> = keyof T['queries'] & string;
1421
1421
  /**
1422
- * Get settings group names from a schema
1422
+ * Get settings group names from a schema.
1423
+ *
1424
+ * `settingsGroups`, `assetTypes` and `locales` are optional members of `SchemaDefinition`, so
1425
+ * indexing one yields `X | undefined`, which never extends the record — every typed client's
1426
+ * `settings()`, `asset()` and `withLocale()` took `never`. The three read through `NonNullable`.
1423
1427
  */
1424
- type SchemaSettingsGroupNames<T extends AnySchemaDefinition> = T['settingsGroups'] extends Record<string, SettingsGroupDefinition> ? keyof T['settingsGroups'] & string : never;
1428
+ type SchemaSettingsGroupNames<T extends AnySchemaDefinition> = NonNullable<T['settingsGroups']> extends Record<string, SettingsGroupDefinition> ? keyof NonNullable<T['settingsGroups']> & string : never;
1425
1429
  /**
1426
1430
  * Get asset type names from a schema
1427
1431
  */
1428
- type SchemaAssetTypeNames<T extends AnySchemaDefinition> = T['assetTypes'] extends Record<string, AssetTypeDefinition> ? keyof T['assetTypes'] & string : never;
1432
+ type SchemaAssetTypeNames<T extends AnySchemaDefinition> = NonNullable<T['assetTypes']> extends Record<string, AssetTypeDefinition> ? keyof NonNullable<T['assetTypes']> & string : never;
1429
1433
  /**
1430
1434
  * Get locale codes from a schema
1431
1435
  */
1432
- type SchemaLocaleCodes<T extends AnySchemaDefinition> = T['locales'] extends readonly LocaleDefinition[] ? T['locales'][number]['code'] : never;
1436
+ type SchemaLocaleCodes<T extends AnySchemaDefinition> = NonNullable<T['locales']> extends readonly LocaleDefinition[] ? NonNullable<T['locales']>[number]['code'] : never;
1433
1437
 
1434
1438
  export { type VideoCaptionsConfig as $, type SettingsGroupConfig as A, type SettingsGroupDefinition as B, type ComponentConfig as C, type SettingsGroupMetadata as D, type InferSettingsGroup as E, defineAssetType as F, type AssetTypeConfig as G, type AssetTypeConfigWithFields as H, type InferSingletonContentType as I, type AssetTypeConfigWithFieldsets as J, type AssetTypeDefinition as K, type LocaleOptions as L, type InferAssetType as M, type ImageKindConfig as N, type FileKindConfig as O, type ImageTransform as P, type ImageFitMode as Q, type ImageOutputFormat as R, type SchemaConfig as S, type TextDirection as T, type ImageUploadConfig as U, type VideoKindConfig as V, type ImageSourceConfig as W, type ImageExifConfig as X, type VariantDefinition as Y, type VideoUploadConfig as Z, type VideoPosterConfig as _, type SchemaDefinition as a, type VideoDownloadConfig as a0, type VideoWatermarkConfig as a1, type FileUploadConfig as a2, type FileScanConfig as a3, defineRole as a4, isRoleDefinition as a5, isBuiltInRole as a6, getBuiltInRoleActions as a7, BUILT_IN_ROLES as a8, ROLE_ACTIONS as a9, type WhereCondition as aA, type WhereValue as aB, type WhereOperator as aC, type SortCondition as aD, type SortDirection as aE, type IncludeDefinition as aF, type SelectMapping as aG, type FilterDefinition as aH, type CachePolicy as aI, type CachePolicyObject as aJ, type IncludeSubQuery as aK, type IncludeSubQueries as aL, type Parameterizable as aM, type QueryParameter as aN, type QueryParameterType as aO, type QueryCardinality as aP, type InferQueryResponse as aQ, type InferQueryParams as aR, BUILT_IN_ROLE_ACTIONS as aa, type BuiltInRole as ab, type RoleAction as ac, type ResourcePermission as ad, type FieldVisibility as ae, type RoleConfig as af, type RoleDefinition as ag, defineView as ah, type ViewScopeConfig as ai, type ContentTypeScopeConfig as aj, type ContentTypesScopeConfig as ak, type EntryTypeScopeConfig as al, type AllScopeConfig as am, type ViewFilterOperator as an, type ViewFilterCondition as ao, type ViewFilterGroup as ap, type ViewFilter as aq, type ViewConfig as ar, type ViewDefinition as as, defineQuery as at, type QueryConfig as au, type QueryDefinition as av, type FetchBlock as aw, type AggregateBlock as ax, type SearchBlock as ay, type SimilarBlock as az, type SchemaContentTypeNames as b, type SchemaSingletonNames as c, defineSchema as d, type SchemaQueryNames as e, type SchemaLocaleCodes as f, type SchemaSettingsGroupNames as g, type SchemaAssetTypeNames as h, defineLocale as i, isLocaleDefinition as j, getLocaleCodes as k, findDefaultLocale as l, getActiveLocales as m, buildFallbackChain as n, type LocaleDefinition as o, defineSingletonContentType as p, defineSingleton as q, type SingletonContentTypeDefinition as r, type SingletonMetadata as s, type InferSingleton as t, defineComponent as u, type ComponentDefinition as v, type ComponentMetadata as w, type ComponentReusabilityMetadata as x, type ComponentValidationMetadata as y, defineSettingsGroup as z };