@relyio/sdk 0.13.0 → 0.15.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 +150 -0
  2. package/dist/{chunk-DEDIJKHY.cjs → chunk-3BHDGUNS.cjs} +1554 -3250
  3. package/dist/{chunk-3NPPMGB6.js → chunk-5UMY6CS4.js} +2 -2
  4. package/dist/{chunk-EW23SZPX.js → chunk-PCFTJLEP.js} +540 -262
  5. package/dist/{chunk-6NGJXLOR.js → chunk-PXA5ONIU.js} +1553 -3240
  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-B2gQZkTy.d.cts} +3697 -2585
  13. package/dist/{include-paths-Cb7iPCW2.d.ts → define-content-type-Ds5rCAYl.d.ts} +3697 -2585
  14. package/dist/{define-schema-sWJ8Zn7C.d.cts → define-schema-Cm_5y3ei.d.cts} +238 -356
  15. package/dist/{define-schema-UHViXKiv.d.ts → define-schema-CxpIYFFR.d.ts} +238 -356
  16. package/dist/index-DQ7eTUPQ.d.ts +3090 -0
  17. package/dist/{index--_u1nPHv.d.cts → index-DY64WjZM.d.cts} +2 -2
  18. package/dist/{index-8DozAWsA.d.ts → index-Db3_tmYf.d.ts} +2 -2
  19. package/dist/index-F0UuHpRj.d.cts +3090 -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
@@ -1,3717 +0,0 @@
1
- import { cj as MergedRelyIOConfig, bC as ApiPath, bD as ApiMethod, ck as PathParamsOf, ca as ApiResponse, bF as Wire, c9 as RequestConfig, bL as BaseContent, ae as Reading, ai as UntypedReading, cl as ReadWhere, bO as WhereClause, bS as QueryOptions, aa as CheckedIncludes, af as ReadWith, bR as SortOptions, bX as ContentQueryResult, bT as CreateInput, bW as UpdateInput, bU as RevisedContent, bV as RestoredContent, bx as SDKError, cg as BaseValidationError, cf as ValidationViolation, aJ as AssetServedBlocks, cm as CheckedInclude, cn as components, bJ as AssetFileBlock, co as paths, by as RelyIOConfig, aq as InferContentType, ac as ContentReading, aH as InferAssetType, a9 as AssetReading, ay as InferSingleton, ah as SingletonReading, aD as InferSettingsGroup, ag as SettingsReading, bz as RelyIOConfigUpdate } from './include-paths-Bs5FhNw2.cjs';
2
- import { a as SchemaDefinition, b as SchemaContentTypeNames, j as SchemaEntryOf, a8 as FilterOperator, h as SchemaAssetTypeNames, c as SchemaSingletonNames, g as SchemaSettingsGroupNames, e as SchemaQueryNames, f as SchemaLocaleCodes } from './define-schema-sWJ8Zn7C.cjs';
3
- import { VisitorResyncReason } from './live-visitor.js';
4
- import { DevUnreadableReason } from './dev-handshake/index.cjs';
5
-
6
- /**
7
- * Dev-mode detection for `relyio dev` sandboxes — the SDK's I/O shell over `../dev-handshake`.
8
- *
9
- * Every rule about the handshake — the path, the env names, the required fields, the deadline —
10
- * lives in `@relyio/sdk/dev-handshake`, which `relyio dev` imports too. What is left here is the
11
- * part that is genuinely the SDK's: reading env and walking the filesystem from the cwd, both of
12
- * which have to happen without a static `node:*` import so the module stays browser-safe.
13
- *
14
- * Node access goes through `process.getBuiltinModule` (Node ≥ 20.16 / 22, and the platform targets
15
- * Node 24), which is absent in browsers and edge runtimes — so the resolver is inert everywhere it
16
- * should be, and a bundler never has to shim `fs`.
17
- *
18
- * **Order: env, then file.** The wrapped-child env is the primary carrier and wins. Before REL-647
19
- * it won *unconditionally*, because it had no deadline to lose on; it now goes through the same
20
- * rule the file does.
21
- */
22
-
23
- /**
24
- * What the handshake says about the sandbox, as the preview toolbar displays it
25
- * (`sdk-surfaces-spec.md` decision 22). `TypedRelyIOClient.sandbox` returns it.
26
- *
27
- * **Plain data, on purpose.** In Next the toolbar is rendered from a server layout, and a client
28
- * cannot cross the RSC boundary — while the browser cannot read the handshake at all, because this
29
- * module is inert without `process.getBuiltinModule`. So the server-created client's `sandbox` is the
30
- * prop the toolbar is handed, and it has to survive `JSON` unchanged: strings and `null`, dates as
31
- * the ISO strings the handshake carried, no class instances.
32
- *
33
- * **No key.** The handshake also carries the sandbox's preview key; that is the client's credential,
34
- * held beside this value in {@link DevResolution} and never inside it, so handing the state around
35
- * does not hand the key with it.
36
- *
37
- * - `active` — the client targets the sandbox. `branchId` is `null` from the env carrier, which names
38
- * no branch; only `.relyio/dev.json` does. `expiresAt` is `null` for an unbounded sandbox.
39
- * - `expired` / `unreadable` — a handshake was found and refused; the client targets the configured
40
- * dataset and key, and `mergeConfig` has said so on the console.
41
- * - `absent` — no handshake on either carrier. This is also what a browser or edge runtime reads,
42
- * since neither can see the env or the file.
43
- * - `disabled` — detection did not run: `dev: false` was passed, or `NODE_ENV === 'production'`
44
- * and `dev: true` was not.
45
- */
46
- type SandboxState = {
47
- state: 'active';
48
- datasetId: string;
49
- branchId: string | null;
50
- expiresAt: string | null;
51
- source: 'env' | 'file';
52
- } | {
53
- state: 'expired';
54
- expiredAt: string;
55
- } | {
56
- state: 'unreadable';
57
- reason: DevUnreadableReason;
58
- } | {
59
- state: 'absent';
60
- } | {
61
- state: 'disabled';
62
- };
63
- /** The sandbox the client targets, once a carrier has been accepted. */
64
- type ActiveSandbox = Extract<SandboxState, {
65
- state: 'active';
66
- }>;
67
-
68
- /**
69
- * What a call site supplies beside the route it named.
70
- *
71
- * `path` is required exactly when the document's template has segments, and
72
- * forbidden when it does not — so a call that forgot to scope a request, or one
73
- * that scoped a request the route does not scope, is a compile error rather
74
- * than a URL with a literal `{id}` in it.
75
- */
76
- type SendOptions<P extends ApiPath, M extends ApiMethod<P>> = {
77
- /** Query string values. The document declares none, so this stays untyped — see `api-routes.ts`. */
78
- query?: Record<string, unknown>;
79
- body?: unknown;
80
- /** Request headers this call adds, e.g. `If-Match` / `Idempotency-Key` (API4). */
81
- headers?: Record<string, string>;
82
- signal?: AbortSignal;
83
- timeout?: number;
84
- } & ([PathParamsOf<P, M>] extends [never] ? {
85
- path?: never;
86
- } : {
87
- path: PathParamsOf<P, M>;
88
- });
89
- /**
90
- * Core HTTP client for the RelyIO SDK.
91
- *
92
- * `send` is **the** typed entry: it names a route by the path template the
93
- * committed document declares, and returns that operation's body type with the
94
- * payload typed as `T`. `readPayload` from `@relyio/api-contract/client` is
95
- * then the one unwrap — there is no second spelling and no runtime test of
96
- * which envelope arrived (`INV-API-CLIENT-IS-DERIVED`).
97
- *
98
- * The verb helpers below stay for the escape hatch `getHttpClient()` exposes;
99
- * nothing inside the SDK uses them.
100
- */
101
- declare class HttpClient {
102
- /** What every request reads off the configuration, derived by {@link configure} and nowhere else. */
103
- private connection;
104
- constructor(config: MergedRelyIOConfig);
105
- /**
106
- * Run on `config` from now on: every later request — whoever holds this client — is sent to its
107
- * `baseUrl`, with its headers, key and dataset, its timeout and retries, through its `fetch`, and
108
- * logged by its `debug` options.
109
- *
110
- * The client's own `updateConfig()` calls this rather than building a second client, so a
111
- * `content()` or other sub-client taken before the update sends through the update too. Anything
112
- * set on this client since it was last configured — `setDefaultHeaders()`, `setBaseUrl()` — is
113
- * replaced by what `config` says.
114
- */
115
- configure(config: MergedRelyIOConfig): void;
116
- private announce;
117
- /**
118
- * Call a route the committed document declares, and get back the body that
119
- * document says it serves.
120
- */
121
- send<P extends ApiPath, M extends ApiMethod<P>, T = unknown>(path: P, method: M, options: SendOptions<P, M>): Promise<ApiResponse<Wire<P, M, T>>>;
122
- /**
123
- * Call a route and return its payload — `send` plus the one unwrap, which is
124
- * what all but a handful of call sites want.
125
- */
126
- fetchPayload<P extends ApiPath, M extends ApiMethod<P>, T = unknown>(path: P, method: M, options: SendOptions<P, M>): Promise<Wire<P, M, T> extends {
127
- data: unknown;
128
- } ? Wire<P, M, T>['data'] : never>;
129
- /**
130
- * Make an HTTP request, retrying only where a retry is safe.
131
- *
132
- * The headers are merged **once**, before the first attempt, and every attempt
133
- * sends that same object. That is what makes a minted `Idempotency-Key` stable
134
- * across the retries of one logical write: the service sees one key and
135
- * answers the replay from the original outcome instead of applying it twice.
136
- *
137
- * The loop always makes one attempt. The incumbent's `while (attempt <
138
- * maxAttempts)` made none at `retryAttempts: 0` — a value the config validator
139
- * then accepted, and now refuses — and then dereferenced an unassigned
140
- * `lastError`. `retryAttempts` is the most attempts, the first included; an
141
- * `HttpClient` constructed directly is not validated, so the first attempt
142
- * stays unconditional.
143
- *
144
- * A request that would carry no `X-API-Key` makes none: it rejects with
145
- * `MissingApiKeyError` before `fetch` is called, is not retried, and names
146
- * `apiKey`. This is where a client built without a key learns it — the
147
- * constructor accepts one so that a module-scope client can be evaluated by a
148
- * build that holds no key (REL-1120) — and so does one whose `setApiKey` was
149
- * given none.
150
- */
151
- request<T = unknown>(config: RequestConfig): Promise<ApiResponse<T>>;
152
- /**
153
- * Make a GET request
154
- */
155
- get<T = unknown>(url: string, params?: Record<string, unknown>): Promise<ApiResponse<T>>;
156
- /**
157
- * Make a POST request
158
- */
159
- post<T = unknown>(url: string, data?: unknown, params?: Record<string, unknown>): Promise<ApiResponse<T>>;
160
- /**
161
- * Make a PUT request
162
- */
163
- put<T = unknown>(url: string, data?: unknown, params?: Record<string, unknown>): Promise<ApiResponse<T>>;
164
- /**
165
- * Make a DELETE request
166
- */
167
- delete<T = unknown>(url: string, params?: Record<string, unknown>): Promise<ApiResponse<T>>;
168
- /**
169
- * Make a PATCH request
170
- */
171
- patch<T = unknown>(url: string, data?: unknown, params?: Record<string, unknown>): Promise<ApiResponse<T>>;
172
- /**
173
- * Add headers to every later request, each replacing any header of the same name — the name
174
- * matched without regard to case — until the client is next configured.
175
- */
176
- setDefaultHeaders(headers: Record<string, string>): void;
177
- /**
178
- * Replace the key every later request sends as `X-API-Key`, and the `apiKey` the configuration
179
- * reports.
180
- *
181
- * The key is read the way construction reads it (`presentKey`): an absent, empty or blank key is
182
- * no key, so the header is dropped and the next request is refused with `MissingApiKeyError`
183
- * before anything is sent. A key header of any other spelling — one given in `headers` — goes
184
- * too, from the headers sent and from the headers the configuration reports, so the old key is
185
- * never sent beside the new one.
186
- *
187
- * The configuration is the one the owning `TypedRelyIOClient` holds (it hands its own to the
188
- * constructor), so its `getConfig().apiKey` — what a surface reads to decide a key's kind — is the
189
- * key sent from here on.
190
- *
191
- * This replaced `setAuthToken`, which added `Authorization: Bearer` beside an `X-API-Key` it
192
- * never touched: the API reads `X-API-Key` first, so the old key was the one used (REL-1385).
193
- */
194
- setApiKey(apiKey: string): void;
195
- /**
196
- * Get current default headers
197
- */
198
- getDefaultHeaders(): Record<string, string>;
199
- /**
200
- * Update base URL
201
- */
202
- setBaseUrl(baseUrl: string): void;
203
- /**
204
- * Get current base URL
205
- */
206
- getBaseUrl(): string;
207
- /**
208
- * One attempt.
209
- *
210
- * The caller's `signal` and the timeout both end it, through one controller.
211
- * The incumbent handed `fetch` the caller's signal **instead of** its own, so a
212
- * request that passed a signal had no timeout at all — and a caller abort then
213
- * surfaced as `Request timed out`. `timedOut` is what tells the two apart: a
214
- * timeout is a `TimeoutError`, a caller abort is the platform's `AbortError`,
215
- * rethrown as it came.
216
- */
217
- private makeRequest;
218
- /**
219
- * The headers without those a browser controls itself, when this is a browser; unchanged when it
220
- * is not.
221
- *
222
- * A name is matched without regard to case (RFC 9110 §5.1), because a browser refuses `Origin`
223
- * whatever case a page spells it in. This matched only the all-lowercase and all-uppercase
224
- * spellings, so `Origin` and `User-Agent` — how people write them — reached `fetch` (REL-1409).
225
- */
226
- private filterHeadersForBrowser;
227
- /**
228
- * Prepare request body based on content type and environment
229
- */
230
- private prepareRequestBody;
231
- private parseResponse;
232
- private resolveUrl;
233
- private mergeRequestConfig;
234
- private buildQueryString;
235
- private headersToObject;
236
- private log;
237
- }
238
-
239
- /**
240
- * The client's locale as a sub-client holds it: a function it asks on every call, or a locale fixed
241
- * when it was built.
242
- *
243
- * `TypedRelyIOClient` hands every sub-client and builder a function over the configuration it holds,
244
- * so one taken before `updateConfig({ locale })` sends the locale the client holds when the call is
245
- * made, and none once `updateConfig({ locale: null })` has cleared it (REL-1453). A string is a locale
246
- * fixed at construction — a sub-client's own `withLocale()`, or one built directly — and stays what it
247
- * was.
248
- */
249
- type ClientLocale = string | (() => string | undefined);
250
-
251
- /**
252
- * Children population options
253
- */
254
- interface ChildrenOptions {
255
- /** Content types of children to include */
256
- contentTypes?: string[];
257
- /** Maximum children per entry */
258
- limit?: number;
259
- /** Recursion depth */
260
- depth?: number;
261
- }
262
- /**
263
- * Retained as an alias of {@link ContentQueryResult}.
264
- *
265
- * It used to add an optional `pagination: { page, pageSize, pageCount, total }`
266
- * that the list route emitted only when `withCount` arrived — "a shape no
267
- * generated type can describe", as that controller's own comment now puts it.
268
- * API2 made the customer list the **cursor** dialect unconditionally
269
- * (`INV-API-ONE-CUSTOMER-PAGE-DIALECT`), so no total reaches this surface and
270
- * the extra member described a body the service cannot send.
271
- */
272
- type ExtendedQueryResult<T extends BaseContent = BaseContent> = ContentQueryResult<T>;
273
- /**
274
- * Fluent query builder for content operations
275
- * Provides a chainable API for building complex content queries
276
- *
277
- * @example
278
- * ```typescript
279
- * const products = await client.content('product')
280
- * .query()
281
- * .where({ status: 'active' })
282
- * .locale('en')
283
- * .fields(['id', 'title', 'price'])
284
- * .include(['brand', 'categories'])
285
- * .sort({ title: 'asc' })
286
- * .limit(20)
287
- * .execute()
288
- * ```
289
- */
290
- declare class ContentQueryBuilder<T extends BaseContent = BaseContent, F extends BaseContent = T, R extends Reading = UntypedReading<T>, I extends string = never> {
291
- private readonly httpClient;
292
- private readonly contentType;
293
- private queryOptions;
294
- private localeValue?;
295
- private localesArray?;
296
- private fetchAllLocales;
297
- private fieldsProjection?;
298
- private childrenOptions?;
299
- private pageNumber?;
300
- private pageSizeValue?;
301
- private requestHeaders?;
302
- /** The client's locale, asked when the query is sent rather than copied when it was built. */
303
- private readonly clientLocale;
304
- private freshValue?;
305
- /**
306
- * @param locale - The client's locale (`withLocale()`, `updateConfig({ locale })`), sent when the
307
- * query names none: the function `TypedRelyIOClient` hands over, or a locale fixed for it
308
- */
309
- constructor(httpClient: HttpClient, contentType: string, locale?: ClientLocale);
310
- /**
311
- * The feed position this read follows — a change's `fresh` (`FeedChange`), or a resync's. Sent as
312
- * `?_fresh`, the one term the API declares for it: no cache — the Content API's edge or the
313
- * browser's own — holds an answer for that position from before it, so the read is answered after
314
- * the change, and every read naming the same position shares that answer. The answer is the
315
- * read's own; omit it for a read no change prompted.
316
- */
317
- fresh(position: string): this;
318
- /**
319
- * Headers this query adds to the client's own, e.g. `X-RelyIO-Dataset` to list another dataset
320
- * the key can see. A name the client already sends is replaced for this query only; the client
321
- * is not changed.
322
- */
323
- headers(headers: Record<string, string>): this;
324
- /**
325
- * Add where conditions to the query, each naming a value by its path: a field the content type
326
- * declares, a field inside an object (`'pricing.basePrice'`), a reference inside its blocks —
327
- * `{ 'sections.features': featureId }` keeps the entries some block of which holds that feature
328
- * (REL-1810) — and rich text that can point at an entry, by the entries it points at. A typed read
329
- * takes exactly the paths the API filters by (`FilterPaths`, REL-2136). A condition on `_metadata`
330
- * is refused here, before anything is sent (`refuseMetadataConditions`).
331
- */
332
- where(conditions: ReadWhere<WhereClause<F>, R>): this;
333
- /**
334
- * Select specific fields to return
335
- */
336
- select(fields: NonNullable<QueryOptions<T, F>['select']>): this;
337
- /**
338
- * Expand references into the entries they point at: one include path per element (REL-1801).
339
- * A path names fields through lists, page builders, objects and references —
340
- * `'sections.features'`, `'sections.customers.logo'` — and may pick the fields each entry returns,
341
- * `'sections.features(name,slug)'`. Each is checked against the schema: a path that holds no
342
- * reference does not compile, and the read's type follows the paths — an expanded reference is the
343
- * entry, every other one its id.
344
- */
345
- include<const P extends readonly string[]>(paths: CheckedIncludes<R, P>): ContentQueryBuilder<Extract<ReadWith<R, I | P[number]>, BaseContent>, F, R, I | P[number]>;
346
- /**
347
- * Add sorting to the query
348
- *
349
- * @example
350
- * ```typescript
351
- * // Object format
352
- * .sort({ createdAt: 'desc', title: 'asc' })
353
- *
354
- * // Field and direction format
355
- * .sort('createdAt', 'desc')
356
- * ```
357
- */
358
- sort(field: string, direction: 'asc' | 'desc'): this;
359
- sort(sortOptions: SortOptions<F>): this;
360
- /**
361
- * Sort by a specific field in ascending order
362
- */
363
- sortAsc(field: keyof T | string): this;
364
- /**
365
- * Sort by a specific field in descending order
366
- */
367
- sortDesc(field: keyof T | string): this;
368
- /**
369
- * Limit the number of results
370
- */
371
- limit(count: number): this;
372
- /**
373
- * Set offset for pagination
374
- */
375
- offset(count: number): this;
376
- /**
377
- * Set cursor for cursor-based pagination
378
- */
379
- cursor(cursor: string): this;
380
- /**
381
- * Set locale for localized content
382
- * @param locale - Locale code (e.g., 'en-US', 'de-DE', 'fr-FR')
383
- *
384
- * @example
385
- * ```typescript
386
- * // Fetch in Spanish
387
- * .locale('es-ES')
388
- * ```
389
- */
390
- locale(locale: string): this;
391
- /**
392
- * Fetch content in multiple specific locales
393
- * Returns primary locale at top level, others in _metadata.localizations
394
- *
395
- * @param localeCodes - Array of locale codes to fetch
396
- *
397
- * @example
398
- * ```typescript
399
- * const product = await client.content('product')
400
- * .findById('prod_123')
401
- * .locales(['en-US', 'es-ES', 'de-DE'])
402
- * .execute()
403
- *
404
- * // Access: product.title (first locale)
405
- * // Access: product._metadata.localizations['es-ES'].title
406
- * ```
407
- */
408
- locales(localeCodes: string[]): this;
409
- /**
410
- * Fetch content in all available locales
411
- * Returns default locale at top level, others in _metadata.localizations
412
- *
413
- * @example
414
- * ```typescript
415
- * const product = await client.content('product')
416
- * .findById('prod_123')
417
- * .allLocales()
418
- * .execute()
419
- *
420
- * // Access: product.title (default locale)
421
- * // Access: product._metadata.localizations['es-ES'].title
422
- * // Access: product._metadata.availableLocales
423
- * ```
424
- */
425
- allLocales(): this;
426
- /**
427
- * Select specific fields to return (projection)
428
- * @param fieldNames - Array of field names to include
429
- *
430
- * @example
431
- * ```typescript
432
- * .fields(['id', 'title', 'price', 'brand.name'])
433
- * ```
434
- */
435
- fields(fieldNames: string[]): this;
436
- /**
437
- * Include children in results (for hierarchical content)
438
- * @param options - Children options or boolean to enable with defaults
439
- *
440
- * @example
441
- * ```typescript
442
- * // Include all children
443
- * .children(true)
444
- *
445
- * // Include with options
446
- * .children({ contentTypes: ['variant'], limit: 10 })
447
- * ```
448
- */
449
- children(options: ChildrenOptions | boolean): this;
450
- /**
451
- * Set page number for page-based pagination
452
- * @param pageNum - Page number (1-based)
453
- */
454
- page(pageNum: number): this;
455
- /**
456
- * Set page size for page-based pagination
457
- * @param size - Items per page
458
- */
459
- pageSize(size: number): this;
460
- /**
461
- * Execute the query and return all matching results
462
- */
463
- execute(): Promise<T[]>;
464
- /**
465
- * Execute the query and return the whole body — the items under `data`, the
466
- * cursor facts under `page`.
467
- */
468
- executeWithMeta(): Promise<ContentQueryResult<T>>;
469
- /**
470
- * Execute the query and return a single result (first match)
471
- */
472
- first(): Promise<T | null>;
473
- /**
474
- * Get paginated results
475
- */
476
- paginate(limit?: number): Promise<ContentQueryResult<T>>;
477
- /**
478
- * Create an async iterator for streaming results
479
- */
480
- [Symbol.asyncIterator](): AsyncIterableIterator<T>;
481
- /**
482
- * Execute the query and return all results as an array (for large datasets)
483
- */
484
- all(): Promise<T[]>;
485
- /**
486
- * Clone the query builder
487
- */
488
- clone(): ContentQueryBuilder<T, F, R, I>;
489
- /**
490
- * Get the current query options
491
- */
492
- getQueryOptions(): QueryOptions<T, F>;
493
- /**
494
- * Execute the query and return the whole body.
495
- *
496
- * Identical to {@link executeWithMeta} now that the list serves one shape;
497
- * kept as the name callers already import.
498
- */
499
- executeWithMetaExtended(): Promise<ExtendedQueryResult<T>>;
500
- /**
501
- * `count()` and `exists()` are gone, and their absence is the honest answer
502
- * rather than an omission (`INV-RD-CAPABILITY-IS-HONEST`).
503
- *
504
- * `count()` sent `withCount` and read `pagination.total`; API2 moved the
505
- * customer list onto the **cursor** dialect, whose page facts are
506
- * `{ nextCursor, hasMore }` and nothing else, so no total reaches this
507
- * surface at all. Keeping the method would have meant either a request that
508
- * always throws or a number assembled from a page the caller happened to
509
- * fetch — a count of what was returned, presented as a count of what matched
510
- * (rule 6). The plan's §3.3 listed `count` among six methods a type would
511
- * repair; what the type actually shows is that the capability is no longer
512
- * there. A caller that wants "is there at least one" fetches one and looks at
513
- * `data.length`; a caller that wants a true total uses `POST /v1/search`,
514
- * which is the offset dialect precisely because a facet-bearing result wants
515
- * one (Decision 5).
516
- */
517
- /**
518
- * Build query parameters for the HTTP request
519
- */
520
- private buildQueryParams;
521
- }
522
- /**
523
- * Specialized query builder for finding by ID
524
- */
525
- declare class ContentByIdBuilder<T extends BaseContent = BaseContent, R extends Reading = UntypedReading<T>, I extends string = never> {
526
- private readonly httpClient;
527
- private readonly contentType;
528
- private readonly id;
529
- private includeRelations?;
530
- private localeValue?;
531
- private localesArray?;
532
- private fetchAllLocales;
533
- private childrenOptions?;
534
- private requestHeaders?;
535
- /** The client's locale, asked when the read is sent rather than copied when it was built. */
536
- private readonly clientLocale;
537
- private freshValue?;
538
- /**
539
- * @param locale - The client's locale (`withLocale()`, `updateConfig({ locale })`), sent when the
540
- * read names none: the function `TypedRelyIOClient` hands over, or a locale fixed for it
541
- */
542
- constructor(httpClient: HttpClient, contentType: string, id: string, locale?: ClientLocale);
543
- /**
544
- * The feed position this read follows — a change's `fresh` (`FeedChange`), or a resync's. Sent as
545
- * `?_fresh`, the one term the API declares for it: no cache — the Content API's edge or the
546
- * browser's own — holds an answer for that position from before it, so the read is answered after
547
- * the change, and every read naming the same position shares that answer. The answer is the
548
- * read's own; omit it for a read no change prompted.
549
- */
550
- fresh(position: string): this;
551
- /**
552
- * Expand references into the entries they point at: one include path per element (REL-1801).
553
- * A path names fields through lists, page builders, objects and references —
554
- * `'sections.features'`, `'sections.customers.logo'` — and may pick the fields each entry returns,
555
- * `'sections.features(name,slug)'`. Each is checked against the schema: a path that holds no
556
- * reference does not compile, and the read's type follows the paths — an expanded reference is the
557
- * entry, every other one its id. A second call adds to the first.
558
- */
559
- include<const P extends readonly string[]>(paths: CheckedIncludes<R, P>): ContentByIdBuilder<Extract<ReadWith<R, I | P[number]>, BaseContent>, R, I | P[number]>;
560
- /**
561
- * Set locale for localized content
562
- * @param locale - Locale code (e.g., 'en-US', 'de-DE', 'fr-FR')
563
- */
564
- locale(locale: string): this;
565
- /**
566
- * Fetch content in multiple specific locales
567
- * Returns primary locale at top level, others in _metadata.localizations
568
- *
569
- * @param localeCodes - Array of locale codes to fetch
570
- */
571
- locales(localeCodes: string[]): this;
572
- /**
573
- * Headers this one read adds to the client's own, e.g. `X-RelyIO-Dataset` to read another dataset
574
- * the key can see. A name the client already sends is replaced for this request only; the client
575
- * is not changed.
576
- */
577
- headers(headers: Record<string, string>): this;
578
- /**
579
- * Fetch content in all available locales
580
- * Returns default locale at top level, others in _metadata.localizations
581
- */
582
- allLocales(): this;
583
- /**
584
- * Include children in results (for hierarchical content)
585
- * @param options - Children options or boolean to enable with defaults
586
- */
587
- children(options: ChildrenOptions | boolean): this;
588
- /**
589
- * Execute the query and return the content item
590
- */
591
- execute(): Promise<T | null>;
592
- }
593
-
594
- /**
595
- * API4's two request headers, exposed the way the SDK's ergonomics already
596
- * expose everything else: as options on the call, not as raw headers.
597
- *
598
- * `apps/services/api` declares them on exactly six and seven routes
599
- * respectively (`CONDITIONAL_WRITE_HEADERS` / `RETRYABLE_WRITE_HEADERS` in that
600
- * service's `contract/routes.ts`), and the API4 closeout is explicit that the
601
- * ten `/file/*` routes and `POST /batch` declare **neither**, because they
602
- * write through `@relyio/asset-files` and the batch planner and would silently
603
- * ignore the header. So these options are on the six and seven methods that
604
- * reach them and nowhere else: a client that could pass `ifMatch` to a route
605
- * that ignores it has been told a lie no gate can catch
606
- * (`INV-RD-CAPABILITY-IS-HONEST`).
607
- *
608
- * The tag itself comes from `X-Entity-Tag`, which the single-entity reads serve
609
- * and the list routes deliberately do not — one header cannot name every
610
- * member's per-locale version. `entityTagOf` reads it off the response the SDK
611
- * already returns, so the round trip is "read one, write it back" with no
612
- * assembly from `_metadata` in between
613
- * (`INV-API-CONDITIONAL-WRITE-IS-REACHABLE`).
614
- */
615
- interface RetryableWriteOptions {
616
- /**
617
- * A key that makes a retry of **this** write safe.
618
- *
619
- * A replay inside the service's window returns the original outcome rather
620
- * than applying twice. The service derives that response from committed state
621
- * pinned to the recorded version — it does not store bytes — so a replay after
622
- * the version has been pruned is refused `RESOURCE_EXPIRED` rather than
623
- * answered from a stale copy.
624
- *
625
- * **The SDK mints one when you do not.** It used to be caller-supplied and
626
- * nothing else, which meant the default write carried no key while the retry
627
- * loop replayed it anyway — the one mechanism that makes a replay safe was
628
- * optional on the one path that needed it. Supply your own to make a key
629
- * outlive the process: a job that may be restarted wants a key derived from
630
- * the work, not one minted per call.
631
- */
632
- idempotencyKey?: string;
633
- }
634
- /**
635
- * The six routes that additionally take `If-Match`.
636
- *
637
- * A creation has no prior entity to precondition on, so `POST /v1/{contentType}`
638
- * takes `Idempotency-Key` and not this — which is why the two option types are
639
- * separate rather than one optional-everything bag.
640
- */
641
- interface ConditionalWriteOptions extends RetryableWriteOptions {
642
- /**
643
- * The `X-Entity-Tag` value a prior read served, sent back as `If-Match`.
644
- *
645
- * The write applies only if that locale of that entry is still at the version
646
- * the tag names; otherwise the service refuses `RESOURCE_CONFLICT` and names
647
- * both versions.
648
- */
649
- ifMatch?: string;
650
- }
651
- /**
652
- * The entity tag a single-entity read served, or `undefined` where the route
653
- * serves none — a list, or a cache HIT, which cannot know the current version
654
- * and honestly says nothing rather than serving a stale tag.
655
- */
656
- declare function entityTagOf(response: {
657
- headers: Record<string, string>;
658
- }): string | undefined;
659
-
660
- /**
661
- * Content fetch options
662
- */
663
- interface ContentFetchOptions<T extends BaseContent = BaseContent, R extends Reading = UntypedReading<T>> {
664
- /** Filter conditions: what `query().where()` takes (REL-2136) */
665
- where?: ReadWhere<WhereClause<T>, R>;
666
- /** Maximum entries to return */
667
- limit?: number;
668
- /** Entries to skip (offset pagination) */
669
- offset?: number;
670
- /** Sort configuration */
671
- sort?: Record<string, 'asc' | 'desc'>;
672
- /**
673
- * Include paths: the references to expand (REL-1801) — see `ContentQueryBuilder.include`. Typed
674
- * where `fetch` is called with them inline.
675
- */
676
- include?: readonly string[];
677
- /** Locale for localized content */
678
- locale?: string;
679
- /** Fields to select (projection) */
680
- fields?: string[];
681
- /** Children population options */
682
- children?: ChildrenOptions | boolean;
683
- }
684
- /**
685
- * Content client for managing content operations
686
- * Provides CRUD operations and query building for a specific content type
687
- *
688
- * @example
689
- * ```typescript
690
- * const products = client.content('product')
691
- *
692
- * // Query with fluent API
693
- * const active = await products.query()
694
- * .where({ status: 'active' })
695
- * .locale('en')
696
- * .include(['brand'])
697
- * .limit(20)
698
- * .execute()
699
- *
700
- * // Quick fetch
701
- * const all = await products.fetchAll()
702
- * ```
703
- */
704
- declare class ContentClient<T extends BaseContent = BaseContent, R extends Reading = UntypedReading<T>> {
705
- private readonly httpClient;
706
- private readonly contentType;
707
- /** The client's locale, asked on every call rather than copied when this client was taken. */
708
- private readonly clientLocale;
709
- /**
710
- * @param locale - The client's locale: the function `TypedRelyIOClient` hands over, asked on
711
- * every call, or a locale fixed for this client
712
- */
713
- constructor(httpClient: HttpClient, contentType: string, locale?: ClientLocale);
714
- /**
715
- * Create a new query builder for this content type
716
- */
717
- query(): ContentQueryBuilder<T, T, R>;
718
- /**
719
- * Find content by ID with fluent options
720
- */
721
- findById(id: string): ContentByIdBuilder<T, R>;
722
- /**
723
- * Fetch all content items
724
- * @param locale - Optional locale for localized content
725
- */
726
- fetchAll(locale?: string): Promise<T[]>;
727
- /**
728
- * Fetch content with query options
729
- */
730
- fetch<const P extends readonly string[]>(options: Omit<ContentFetchOptions<T, R>, 'include'> & {
731
- include: CheckedIncludes<R, P>;
732
- }): Promise<Array<ReadWith<R, P[number]>>>;
733
- fetch(options?: Omit<ContentFetchOptions<T, R>, 'include'>): Promise<T[]>;
734
- /**
735
- * Fetch a single content item by ID
736
- * @param id - Entry ID or slug
737
- * @param options - Fetch options (include, locale, and `headers` this one request adds — e.g.
738
- * `{ 'X-RelyIO-Dataset': 'staging' }` to read a dataset other than the client's)
739
- */
740
- fetchOne<const P extends readonly string[]>(id: string, options: {
741
- include: CheckedIncludes<R, P>;
742
- locale?: string;
743
- headers?: Record<string, string>;
744
- }): Promise<ReadWith<R, P[number]> | null>;
745
- fetchOne(id: string, options?: {
746
- locale?: string;
747
- headers?: Record<string, string>;
748
- }): Promise<T | null>;
749
- /**
750
- * Fetch one entry **and the entity tag that read served**.
751
- *
752
- * This is the read half of a conditional write. `X-Entity-Tag` is minted from
753
- * `(entryUid, locale, localeVersion)` and is exactly what `If-Match` takes,
754
- * so a caller reads here, edits, and hands the same string back to `update`
755
- * — with nothing to assemble in between
756
- * (`INV-API-CONDITIONAL-WRITE-IS-REACHABLE`).
757
- *
758
- * `entityTag` is `undefined` when the response came from the cache: a cached
759
- * body cannot know the current version, and an absent tag beats a stale one
760
- * that would refuse the next write.
761
- *
762
- * Unlike {@link fetchOne}, a missing entry is not `null`: the service's
763
- * `RESOURCE_NOT_FOUND` is thrown as it arrived, with the `kind` that says
764
- * whether the entry, the content type or the schema was missing. `headers`
765
- * are the ones this read adds, as for `fetchOne`.
766
- */
767
- fetchOneWithTag<const P extends readonly string[]>(id: string, options: {
768
- include: CheckedIncludes<R, P>;
769
- locale?: string;
770
- headers?: Record<string, string>;
771
- }): Promise<{
772
- entry: ReadWith<R, P[number]>;
773
- entityTag?: string;
774
- }>;
775
- fetchOneWithTag(id: string, options?: {
776
- locale?: string;
777
- headers?: Record<string, string>;
778
- }): Promise<{
779
- entry: T;
780
- entityTag?: string;
781
- }>;
782
- /**
783
- * Fetch the first content item matching the conditions
784
- * @param where - Filter conditions
785
- * @param locale - Optional locale
786
- */
787
- fetchFirst(where?: ReadWhere<WhereClause<T>, R>, locale?: string): Promise<T | null>;
788
- /**
789
- * Create a new content item
790
- * @param data - Content data
791
- * @param options - Create options (slug, locale, parentId, sortOrder), and `headers` this one
792
- * request adds — e.g. `{ 'X-RelyIO-Dataset': 'staging' }` to write a dataset other than the
793
- * client's. They are sent, never put in the body; `idempotencyKey` still wins over an
794
- * `Idempotency-Key` among them. `locale` defaults to the client's own (`withLocale()`).
795
- */
796
- create(data: CreateInput<T>, options?: {
797
- slug?: string;
798
- locale?: string;
799
- parentId?: string;
800
- sortOrder?: number;
801
- headers?: Record<string, string>;
802
- } & RetryableWriteOptions): Promise<T>;
803
- /**
804
- * Update a content item by ID (partial update)
805
- * @param id - Entry ID
806
- * @param data - Fields to update
807
- * @param options - Update options (slug, locale, parentId, sortOrder), and `headers` this one
808
- * request adds, as for {@link create}. `ifMatch` and `idempotencyKey` win over the same
809
- * header among them. `locale` defaults to the client's own, the locale whose entity tag
810
- * {@link fetchOneWithTag} reads, so the tag it hands back is checked against the same locale.
811
- * @returns The entry, its `_metadata.brokenLinks` naming each entry link another entry publishes
812
- * that this revision left pointing nowhere — empty when it took no heading away.
813
- */
814
- update(id: string, data: UpdateInput<T>, options?: {
815
- slug?: string;
816
- locale?: string;
817
- parentId?: string;
818
- sortOrder?: number;
819
- headers?: Record<string, string>;
820
- } & ConditionalWriteOptions): Promise<RevisedContent<T>>;
821
- /**
822
- * Delete a content item by ID
823
- */
824
- delete(id: string, options?: ConditionalWriteOptions): Promise<void>;
825
- /**
826
- * Check if content exists by ID
827
- */
828
- exists(id: string): Promise<boolean>;
829
- /**
830
- * Get version history for one locale of an entry.
831
- *
832
- * Versions are numbered per locale, so this is one locale's timeline: `locale` defaults to the
833
- * client's own (`withLocale()`), else the schema default; no fallback is applied, and a locale
834
- * with no history returns an empty array rather than another locale's rows. Every row names the
835
- * locale it belongs to.
836
- */
837
- versions(id: string, options?: {
838
- limit?: number;
839
- locale?: string;
840
- }): Promise<Array<T & {
841
- versionNumber: number;
842
- locale: string;
843
- }>>;
844
- /**
845
- * Get a specific version of an entry.
846
- *
847
- * `locale` defaults to the client's own (`withLocale()`), else the schema default. Version
848
- * numbers are per locale, so version 2 is different content in each one.
849
- */
850
- version(id: string, versionNumber: number, options?: {
851
- locale?: string;
852
- }): Promise<T | null>;
853
- /**
854
- * Revert one locale of an entry to a previous version.
855
- *
856
- * `locale` is **required**, and that is not a convention: versions are numbered per locale, so
857
- * `versionNumber` alone is ambiguous, and defaulting it would silently revert the wrong
858
- * translation. Only that locale's fields change — shared fields and every other locale are left
859
- * untouched.
860
- *
861
- * The restore is validated as strictly as a publish, so it cannot publish invalid content, and it
862
- * records a new version of its own rather than rewinding the counter — so the revert is auditable
863
- * and itself reversible.
864
- *
865
- * @returns The entry, its `_metadata.leftOut` naming each value of the version the active schema
866
- * no longer declares, and `_metadata.brokenLinks` the entry links it left pointing nowhere.
867
- */
868
- restore(id: string, versionNumber: number, locale: string, options?: ConditionalWriteOptions): Promise<RestoredContent<T>>;
869
- /**
870
- * Get the direct children of a hierarchical entry. The route lists one level and takes no depth
871
- * (REL-2195): `{ depth }` was sent as `?depth=`, which the API refused with `400`. A tree nested
872
- * under each entry is a list or entry read's `query().children()`.
873
- * @param id - Parent entry ID
874
- * @param options - Include paths to expand on each child, and the locale to read
875
- */
876
- children(id: string, options?: {
877
- include?: string[];
878
- locale?: string;
879
- }): Promise<T[]>;
880
- /**
881
- * Get the content type name
882
- */
883
- getContentType(): string;
884
- /**
885
- * Create a new ContentClient for a different content type
886
- */
887
- ofType<U extends BaseContent = BaseContent>(contentType: string): ContentClient<U>;
888
- /**
889
- * Create a new ContentClient with a different default locale, fixed: an update of the client's
890
- * locale (`updateConfig({ locale })`) does not reach it
891
- */
892
- withLocale(locale: string): ContentClient<T, R>;
893
- /**
894
- * Get the default locale as it is now: the client's, or the one `withLocale()` fixed
895
- */
896
- getDefaultLocale(): string | undefined;
897
- }
898
-
899
- /**
900
- * Upload/processing error taxonomy (plan 17, 00b Step 4).
901
- *
902
- * A **thin customer-side mirror** of the live-contracts `FileErrorCode`
903
- * (correction C6 — the SDK must not import the server/Studio contracts package).
904
- * Keep the members in sync with `@relyio/live-contracts`.
905
- *
906
- * Written once, as values, because a processing failure's code arrives as a string the service
907
- * stored and has to be recognised at runtime; `FileErrorCode` is read off this list. The module is
908
- * internal — `errors/index.ts` does not re-export it — so the list is not a second public name for
909
- * the type.
910
- */
911
- declare const FILE_ERROR_CODES: readonly ["UPLOAD_TOO_LARGE", "UNSUPPORTED_MIME", "DIMENSION_OUT_OF_RANGE", "ASPECT_RATIO", "CORRUPT_FILE", "UNSUPPORTED_CODEC", "IMAGES_LIMIT_EXCEEDED", "STREAM_ENCODE_FAILED", "SCAN_INFECTED", "INTERNAL"];
912
-
913
- /**
914
- * Upload/processing error taxonomy (plan 17, 00b Step 4): a customer-side mirror of the
915
- * live-contracts `FileErrorCode`, read off the list in `./file-error-codes`.
916
- */
917
- type FileErrorCode = (typeof FILE_ERROR_CODES)[number];
918
- /**
919
- * The code a {@link FileUploadError} carries: a {@link FileErrorCode}, or `PROCESSING_FAILED` for a
920
- * processing failure this SDK cannot name — the service stored no code, as an image or file
921
- * processing failure is stored today, or one this version of the SDK predates. What the service
922
- * stored is in the error's `context.processingError` either way.
923
- */
924
- type FileUploadErrorCode = FileErrorCode | 'PROCESSING_FAILED';
925
- /**
926
- * A failure during the upload protocol (presign / PUT / multipart / confirm) or
927
- * surfaced by the server's processing pipeline. Carries a typed {@link FileUploadErrorCode}
928
- * and a `retriable` hint so callers (and `upload()`'s bounded auto-retry) can
929
- * decide whether to retry on the same entry.
930
- */
931
- declare class FileUploadError extends SDKError {
932
- readonly code: FileUploadErrorCode;
933
- readonly retryable: boolean;
934
- constructor(code: FileUploadErrorCode, message: string, options?: {
935
- retriable?: boolean;
936
- statusCode?: number;
937
- cause?: Error;
938
- context?: Record<string, unknown>;
939
- });
940
- getUserMessage(): string;
941
- }
942
- /**
943
- * Client-side file validation failure (MIME/size/etc.) raised **before** any
944
- * bytes leave the machine (UX only — the server presign stays authoritative).
945
- */
946
- declare class FileValidationError extends BaseValidationError {
947
- readonly code = "FILE_VALIDATION_ERROR";
948
- constructor(message: string, violations?: ValidationViolation[], cause?: Error);
949
- getUserMessage(): string;
950
- }
951
- /** Constraints `validateFile` checks a file against (subset of the schema upload gate). */
952
- interface FileValidationConstraints {
953
- /** Allowed MIME types (exact match). */
954
- allowedMimeTypes?: string[];
955
- /** Max size in bytes. */
956
- maxSizeBytes?: number;
957
- }
958
- /**
959
- * Validate a file against explicit constraints, returning typed violations.
960
- *
961
- * The customer SDK does not carry a `SchemaReader` (that lives in the Studio
962
- * layer), so callers pass the constraints they want enforced client-side; the
963
- * server presign remains the authority. Returns an empty array when valid.
964
- */
965
- declare function validateFile(file: {
966
- type?: string;
967
- size?: number;
968
- }, constraints?: FileValidationConstraints): ValidationViolation[];
969
-
970
- /** Delivery visibility for a file binding (gateway-enforced, no byte relocation). */
971
- type AssetVisibility = 'public' | 'protected' | 'private';
972
- /** Binary lifecycle status — independent of the entry's publishing status. */
973
- type FileStatus = 'pending_upload' | 'uploading' | 'processing' | 'ready' | 'failed';
974
- /** Storage backend: `r2` = image + file kinds, `stream` = video kind. */
975
- type FileBackend = 'r2' | 'stream';
976
- /** Extracted media metadata (Phase 1 basics + Phase 2 enrichment, plans 15–19); open-ended. */
977
- type AssetFileExtracted = NonNullable<AssetFileBlock['extracted']>;
978
- /**
979
- * Where one file binding's upload got to, as {@link AssetFileBuilder.status} reads it
980
- * (`GET /v1/{assetType}/{id}/file`): the document's `FileStatus` — the binding's lifecycle and
981
- * nothing of the entry's content. `processing.error` is present exactly while `status` is `failed`.
982
- */
983
- type FileStatusResult = components['schemas']['FileStatus'];
984
-
985
- /**
986
- * An asset entry response: schema fields at the top level plus the reserved
987
- * `file` + `urls` blocks and `_metadata`. `T` carries the inferred field shape — for a typed client
988
- * `InferAssetType`, so this is `InferAssetEntry`, the shape an expanded asset reads as too.
989
- */
990
- type AssetEntryResponse<T extends BaseContent = BaseContent> = T & AssetServedBlocks;
991
- /** Request a single upload target for the binding. */
992
- interface PresignInput {
993
- /** Original file name (required). */
994
- fileName: string;
995
- /** MIME type the client will upload with. */
996
- contentType?: string;
997
- /** File size in bytes (early upload-gate validation). */
998
- size?: number;
999
- /** Locale to target for file-localized asset types. */
1000
- locale?: string;
1001
- /** Advisory client-computed SHA-256 (duplicate detection). */
1002
- hash?: string;
1003
- }
1004
- /** Single upload target returned by `presign`. */
1005
- interface PresignResult {
1006
- backend: FileBackend;
1007
- /** `put` = single PUT / Stream POST; `multipart` = resumable R2 session (plan 17). */
1008
- mode?: 'put' | 'multipart';
1009
- /** R2 presigned PUT (image/file) or Stream direct-creator URL (video). */
1010
- uploadUrl?: string;
1011
- method: 'PUT' | 'POST';
1012
- headers?: Record<string, string>;
1013
- /** Object key for the binding — present for R2 (needed to drive multipart). */
1014
- objectKey?: string;
1015
- storageVersion?: number;
1016
- /** Multipart upload id — present only for `mode: 'multipart'`. */
1017
- uploadId?: string;
1018
- /** Multipart part size in bytes — present only for `mode: 'multipart'`. */
1019
- partSize?: number;
1020
- expiresAt: string;
1021
- locale?: string;
1022
- }
1023
- /** Result of opening a multipart session (`file(uid).createMultipart()`). */
1024
- interface MultipartCreateResult {
1025
- uploadId: string;
1026
- objectKey: string;
1027
- partSize: number;
1028
- storageVersion: number;
1029
- locale?: string;
1030
- }
1031
- /** A single uploaded part (`{ PartNumber, ETag }` ordering for `complete`). */
1032
- interface MultipartPart {
1033
- partNumber: number;
1034
- etag: string;
1035
- size?: number;
1036
- }
1037
- /** Target a multipart session in subsequent ops. */
1038
- interface MultipartTarget {
1039
- uploadId: string;
1040
- objectKey: string;
1041
- locale?: string;
1042
- }
1043
- /** Upload progress tick handed to `.onProgress()`. */
1044
- interface UploadProgress {
1045
- /** `upload` for client → storage bytes. */
1046
- phase: 'upload';
1047
- /** Bytes transferred so far. */
1048
- loaded: number;
1049
- /** Total bytes (when known). */
1050
- total: number;
1051
- /** 0–100 integer. */
1052
- progress: number;
1053
- }
1054
- /** Acknowledge a completed client upload. */
1055
- interface ConfirmInput {
1056
- locale?: string;
1057
- hash?: string;
1058
- }
1059
- /** Result of `confirm`. */
1060
- interface ConfirmResult {
1061
- accepted: boolean;
1062
- }
1063
- /** Resolve a single gateway delivery URL. */
1064
- interface ResolveUrlInput {
1065
- locale?: string;
1066
- /** Named image variant id. */
1067
- variantId?: string;
1068
- /** Video delivery target. */
1069
- purpose?: 'playback' | 'poster' | 'download';
1070
- }
1071
- /** Result of `url`. `expiresAt` is omitted for `public` bindings. */
1072
- interface ResolveUrlResult {
1073
- url: string;
1074
- expiresAt?: string;
1075
- }
1076
- /** Options when creating an asset entry. */
1077
- interface CreateAssetOptions {
1078
- slug?: string;
1079
- locale?: string;
1080
- /** Original file name (also settable at presign). */
1081
- fileName?: string;
1082
- /** Initial delivery visibility (default `public`). */
1083
- visibility?: AssetVisibility;
1084
- /** Auto-publish the entry when the file becomes ready (default `true`). */
1085
- publish?: boolean;
1086
- }
1087
- /** Options for {@link AssetClient.update}: the entry route takes both API4 headers. */
1088
- type UpdateAssetOptions = {
1089
- slug?: string;
1090
- locale?: string;
1091
- } & ConditionalWriteOptions;
1092
- /** Options for {@link AssetFileBuilder.waitUntilReady}. */
1093
- interface WaitUntilReadyOptions {
1094
- /** Locale of the binding to watch. */
1095
- locale?: string;
1096
- /**
1097
- * Total time to wait before giving up (ms), from 0 to 2147483647. Default 60000. `0` reads the
1098
- * file once and rejects with a `TimeoutError` if it is not ready.
1099
- */
1100
- timeoutMs?: number;
1101
- /** Delay between polls (ms), from 1 to 2147483647. Default 1500. */
1102
- pollIntervalMs?: number;
1103
- }
1104
- /**
1105
- * Operations on a single asset entry's file binding, backed by the
1106
- * `/{assetType}/{entryUid}/file/*` routes (plan 06 §2).
1107
- */
1108
- declare class AssetFileBuilder<T extends BaseContent = BaseContent> {
1109
- private readonly httpClient;
1110
- private readonly assetType;
1111
- private readonly entryUid;
1112
- /** The client's locale, asked on every call rather than copied when this builder was taken. */
1113
- private readonly clientLocale;
1114
- /**
1115
- * @param locale - The client's locale, sent when a call names none: the function
1116
- * `TypedRelyIOClient` hands over, or a locale fixed for this builder
1117
- */
1118
- constructor(httpClient: HttpClient, assetType: string, entryUid: string, locale?: ClientLocale);
1119
- /**
1120
- * The path parameters every `/file/*` route on this builder takes.
1121
- *
1122
- * The path *template* is the document's; only these two values are the
1123
- * builder's, so a route that gains or loses a segment is a compile error here
1124
- * rather than a hand-built string that silently misses.
1125
- */
1126
- private get target();
1127
- private withLocale;
1128
- /**
1129
- * The `locale` body field of a multipart call: the session's, else the client's. The API finds
1130
- * the binding by it, so a session a `withLocale()` client opened must name the same locale on
1131
- * every later call or its `objectKey` is refused as another binding's.
1132
- */
1133
- private localeField;
1134
- /**
1135
- * Issue a single upload target — an R2 presigned PUT (image/file) or a
1136
- * Cloudflare Stream direct-creator URL (video). Resumable multipart is plan 17.
1137
- */
1138
- presign(input: PresignInput): Promise<PresignResult>;
1139
- /**
1140
- * Acknowledge a completed client upload (kicks off the fast-path intake for
1141
- * R2; a no-op for Stream, whose readiness is webhook-driven).
1142
- */
1143
- confirm(input?: ConfirmInput): Promise<ConfirmResult>;
1144
- /** Open an R2 multipart session for a large file (standalone of `presign`). */
1145
- createMultipart(input: PresignInput): Promise<MultipartCreateResult>;
1146
- /** Presign a single part PUT (1-based `partNumber`). */
1147
- signPart(target: MultipartTarget & {
1148
- partNumber: number;
1149
- }): Promise<{
1150
- url: string;
1151
- }>;
1152
- /** List parts already uploaded (cross-session resume). */
1153
- listParts(target: MultipartTarget): Promise<MultipartPart[]>;
1154
- /** Finalize a multipart session. */
1155
- completeMultipart(target: MultipartTarget & {
1156
- parts: MultipartPart[];
1157
- }): Promise<{
1158
- ok: boolean;
1159
- etag?: string;
1160
- }>;
1161
- /** Abort a multipart session, freeing staged parts. */
1162
- abortMultipart(target: MultipartTarget): Promise<{
1163
- aborted: boolean;
1164
- }>;
1165
- /**
1166
- * Resolve a single gateway delivery URL for the requested locale/variant/purpose.
1167
- * Throws if the binding is not `ready`.
1168
- */
1169
- url(input?: ResolveUrlInput): Promise<ResolveUrlResult>;
1170
- /**
1171
- * Read where this file's upload got to — `status`, and the processing stage, progress and error
1172
- * the pipeline recorded — whatever the asset entry is published in.
1173
- *
1174
- * An asset is a draft until its file is ready, and stays one when its file fails, so a key that
1175
- * reads published content finds no entry for an upload in flight or one that failed. This read
1176
- * answers for the binding itself. It takes the capability the upload calls take, so a read-only
1177
- * key is refused with `ReadOnlyKeyError`, and it never returns the entry's fields.
1178
- */
1179
- status(options?: {
1180
- locale?: string;
1181
- }): Promise<FileStatusResult>;
1182
- /**
1183
- * Update the binding's delivery visibility (token-based; no byte relocation —
1184
- * a downgrade purges the gateway cache). Returns the refreshed asset entry.
1185
- */
1186
- updateVisibility(visibility: AssetVisibility, options?: {
1187
- locale?: string;
1188
- }): Promise<AssetEntryResponse<T>>;
1189
- /**
1190
- * Set the binding's normalized (0..1, origin top-left) smart-crop focal point
1191
- * (plan 18) so `cover`/`crop` variants frame the subject. Per locale for
1192
- * file-localized asset types; token-based (no byte change). Returns the
1193
- * refreshed asset entry.
1194
- */
1195
- setFocalPoint(focalPoint: {
1196
- x: number;
1197
- y: number;
1198
- }, options?: {
1199
- locale?: string;
1200
- }): Promise<AssetEntryResponse<T>>;
1201
- /**
1202
- * Remove the file binding, clean up the underlying bytes, and purge the
1203
- * gateway cache. The asset entry itself is preserved.
1204
- */
1205
- delete(options?: {
1206
- locale?: string;
1207
- }): Promise<void>;
1208
- /**
1209
- * Poll the asset entry until its file binding reaches `ready`, and resolve with the ready entry.
1210
- *
1211
- * Each poll reads the entry through the key's lens. When that finds nothing — a key that reads
1212
- * published content, and an asset that is a draft until its file is ready and stays one when it
1213
- * fails — the poll reads the binding itself ({@link status}), so a failed upload rejects as one
1214
- * rather than running out the limit (REL-1481).
1215
- *
1216
- * @throws {FileUploadError} When processing failed. `code` is the processing code the service
1217
- * stored — `UNSUPPORTED_CODEC`, `CORRUPT_FILE` and so on — or `PROCESSING_FAILED` where it
1218
- * stored none this SDK knows; `context.processingError` holds what it stored, and `retryable`
1219
- * is its `retriable` hint.
1220
- * @throws {TimeoutError} When `timeoutMs` passes first. The file may still become ready; the
1221
- * error's `context.timeout` is the limit and `context.fileStatus` the status last seen. A file
1222
- * that is `ready` in an asset the key's lens never shows — created with `publish: false`, or
1223
- * awaiting a workflow — ends here too, and the message says so.
1224
- * @throws {ReadOnlyKeyError} When the key's lens does not show the asset and the key may not read
1225
- * its binding either: nothing it can read would ever report the file.
1226
- */
1227
- waitUntilReady(options?: WaitUntilReadyOptions): Promise<AssetEntryResponse<T>>;
1228
- }
1229
- /**
1230
- * High-level one-shot upload: create draft entry → presign → PUT bytes →
1231
- * confirm → (optionally) wait for `ready`. Handles the common image/file flow
1232
- * (single R2 PUT). For advanced/resumable flows (large files, video tus,
1233
- * progress) use the lower-level `file(uid)` builder — full upload resilience is
1234
- * plan 17.
1235
- */
1236
- declare class AssetUploadBuilder<T extends BaseContent = BaseContent> {
1237
- private readonly httpClient;
1238
- private readonly assetType;
1239
- private readonly file;
1240
- private _fileName?;
1241
- private _visibility?;
1242
- private _locale?;
1243
- private _publish?;
1244
- private _content;
1245
- private _slug?;
1246
- private _waitForReady;
1247
- private _waitOptions;
1248
- private _onProgress?;
1249
- private _validate?;
1250
- private _maxPartRetries;
1251
- /** The client's locale, read once when the upload starts. */
1252
- private readonly clientLocale;
1253
- /**
1254
- * @param locale - The client's locale, the upload's when `.locale()` names none: the function
1255
- * `TypedRelyIOClient` hands over, or a locale fixed for this upload
1256
- */
1257
- constructor(httpClient: HttpClient, assetType: string, file: Blob, locale?: ClientLocale);
1258
- /** Subscribe to client → storage upload progress (single PUT + multipart). */
1259
- onProgress(callback: (_progress: UploadProgress) => void): this;
1260
- /**
1261
- * Validate the file client-side (UX only) against explicit constraints before
1262
- * any bytes leave the machine. Rejects instantly with a {@link FileValidationError}.
1263
- */
1264
- validate(constraints: FileValidationConstraints): this;
1265
- /** Bounded per-part retry attempts for multipart (default 3). */
1266
- maxPartRetries(attempts: number): this;
1267
- /** Set the original file name (defaults to `file.name` for `File` inputs). */
1268
- filename(name: string): this;
1269
- /** Set the initial delivery visibility (default `public`). */
1270
- visibility(visibility: AssetVisibility): this;
1271
- /** Target locale for file-localized asset types. */
1272
- locale(locale: string): this;
1273
- /** Suppress (or enable) auto-publish on ready. */
1274
- publish(publish: boolean): this;
1275
- /** Set the asset's metadata field values (e.g. `{ alt: '...' }`). */
1276
- content(content: Record<string, unknown>): this;
1277
- /** Set an explicit slug for the new entry. */
1278
- slug(slug: string): this;
1279
- /**
1280
- * Wait until the file binding becomes `ready` before resolving.
1281
- *
1282
- * @throws {Error} When `timeoutMs` or `pollIntervalMs` is one the wait cannot honour — refused
1283
- * here, before anything is uploaded, rather than after the bytes are in.
1284
- */
1285
- waitForReady(options?: boolean | Omit<WaitUntilReadyOptions, 'locale'>): this;
1286
- /** Execute the full upload flow and resolve with the asset entry. */
1287
- execute(): Promise<AssetEntryResponse<T>>;
1288
- /** Single presigned PUT (≤ 50 MB / Stream POST). */
1289
- private uploadSingle;
1290
- /**
1291
- * Resumable R2 multipart: slice into `partSize` chunks, skip already-uploaded
1292
- * parts (resume via `list-parts`), PUT each remaining part with bounded retry,
1293
- * then `complete`. Aborts the session on unrecoverable failure.
1294
- */
1295
- private uploadMultipart;
1296
- /** PUT a single part, retrying transient failures up to `_maxPartRetries`. */
1297
- private putPartWithRetry;
1298
- private emitProgress;
1299
- }
1300
- /**
1301
- * Client for a single asset type. Mirrors {@link ContentClient} (CRUD + query
1302
- * over the same dynamic `/{assetType}` routes — assets are `entryType: 'asset'`
1303
- * entries) and adds the `file(uid)` sub-resource builder + an `upload()` helper.
1304
- */
1305
- /**
1306
- * An asset read typed by the asset type's reading `R`: what the type's include paths make of its
1307
- * fields, beside the `file` and `urls` blocks every asset read carries (REL-1801).
1308
- */
1309
- interface AssetResponseReading<R extends Reading, T extends BaseContent = BaseContent> extends Reading {
1310
- readonly read: AssetEntryResponse<Extract<ReadWith<R, this['include']>, BaseContent>>;
1311
- readonly checked: CheckedInclude<R, this['path']>;
1312
- /**
1313
- * The asset type's own `where` — every path the API filters its fields by, never the `file` and
1314
- * `urls` blocks — or, where no schema types it, the conditions `T`'s keys take (REL-2195).
1315
- */
1316
- readonly where: ReadWhere<WhereClause<T>, R>;
1317
- }
1318
- declare class AssetClient<T extends BaseContent = BaseContent, R extends Reading = UntypedReading<T>> {
1319
- private readonly httpClient;
1320
- private readonly assetType;
1321
- /** The client's locale, asked on every call rather than copied when this client was taken. */
1322
- private readonly clientLocale;
1323
- /**
1324
- * @param locale - The client's locale: the function `TypedRelyIOClient` hands over, asked on
1325
- * every call, or a locale fixed for this client
1326
- */
1327
- constructor(httpClient: HttpClient, assetType: string, locale?: ClientLocale);
1328
- /**
1329
- * Create a query builder for this asset type. It resolves asset reads; its conditions name the
1330
- * asset type's fields, which the `file` and `urls` blocks are not.
1331
- */
1332
- query(): ContentQueryBuilder<AssetEntryResponse<T>, T, AssetResponseReading<R, T>>;
1333
- /** Find an asset entry by id (or slug) with fluent options. */
1334
- findById(id: string): ContentByIdBuilder<AssetEntryResponse<T>, AssetResponseReading<R, T>>;
1335
- /** Fetch a single asset entry by id (or slug), or `null` if not found. */
1336
- fetchOne<const P extends readonly string[]>(id: string, options: {
1337
- include: CheckedIncludes<R, P>;
1338
- locale?: string;
1339
- }): Promise<ReadWith<AssetResponseReading<R, T>, P[number]> | null>;
1340
- fetchOne(id: string, options?: {
1341
- locale?: string;
1342
- }): Promise<AssetEntryResponse<T> | null>;
1343
- /** Fetch the first asset entry matching the conditions, or `null`. */
1344
- fetchFirst(where?: ReadWhere<WhereClause<T>, R>, locale?: string): Promise<AssetEntryResponse<T> | null>;
1345
- /**
1346
- * Create a new asset entry as a draft. The binary is uploaded separately via
1347
- * `file(uid)` (or use the high-level `upload()` helper), and the entry
1348
- * auto-publishes when its file becomes ready (unless `publish: false`).
1349
- * `locale` defaults to the client's own (`withLocale()`).
1350
- */
1351
- create(data: CreateInput<T>, options?: CreateAssetOptions & RetryableWriteOptions): Promise<AssetEntryResponse<T>>;
1352
- /**
1353
- * Update an asset entry's metadata fields (and/or slug). `locale` defaults to the client's own
1354
- * (`withLocale()`).
1355
- */
1356
- update(id: string, data: UpdateInput<T>, options?: UpdateAssetOptions): Promise<AssetEntryResponse<T>>;
1357
- /** Delete an asset entry (cascades file-byte cleanup + gateway purge). */
1358
- delete(id: string, options?: ConditionalWriteOptions): Promise<void>;
1359
- /** Get the file sub-resource builder for a specific asset entry. */
1360
- file(entryUid: string): AssetFileBuilder<T>;
1361
- /** Start a high-level upload (create → presign → PUT → confirm). */
1362
- upload(file: Blob): AssetUploadBuilder<T>;
1363
- /** Get the asset type id. */
1364
- getAssetType(): string;
1365
- /** Get the default locale as it is now: the client's, or the one `withLocale()` fixed. */
1366
- getDefaultLocale(): string | undefined;
1367
- /**
1368
- * Create a new AssetClient for this type with a different default locale, fixed: an update of the
1369
- * client's locale (`updateConfig({ locale })`) does not reach it.
1370
- */
1371
- withLocale(locale: string): AssetClient<T>;
1372
- }
1373
- /**
1374
- * Narrow an entry (e.g. a populated reference from `include`) to an asset entry.
1375
- * Relies on the `_metadata.entryType` discriminator the API emits (plan 06).
1376
- *
1377
- * @example
1378
- * ```typescript
1379
- * const article = await relyio.content('article').fetchOne(id, { include: ['hero'] })
1380
- * if (isAssetEntry(article?.hero)) {
1381
- * console.log(article.hero.urls?.original)
1382
- * }
1383
- * ```
1384
- */
1385
- declare function isAssetEntry<T extends BaseContent = BaseContent>(entry: unknown): entry is AssetEntryResponse<T>;
1386
- /**
1387
- * A reference field populated (via `include`) with a full asset entry. Identical
1388
- * to {@link AssetEntryResponse}; named for readability at reference call sites.
1389
- */
1390
- type PopulatedAssetReference<T extends BaseContent = BaseContent> = AssetEntryResponse<T>;
1391
-
1392
- /**
1393
- * Singleton fetch options
1394
- */
1395
- interface SingletonFetchOptions {
1396
- /** Locale for localized content */
1397
- locale?: string;
1398
- /**
1399
- * Include paths: the references to expand (REL-1801), added to the ones `include()` named — see
1400
- * `include()`. Typed where the call names them inline.
1401
- */
1402
- include?: readonly string[];
1403
- /**
1404
- * The feed position this read follows — a change's `fresh` (`FeedChange`), or a resync's. Sent as
1405
- * `?_fresh`: no cache holds an answer for it from before that position, so the read is answered
1406
- * after the change. The answer is the read's own; omit it for a read no change prompted.
1407
- */
1408
- fresh?: string;
1409
- }
1410
- /**
1411
- * Singleton client for managing singleton content operations
1412
- * Provides a fluent API for singleton operations following the same pattern as ContentClient
1413
- *
1414
- * Every call is in the locale it names — the argument (`fetch({ locale })`, `update(data, locale)`,
1415
- * `versions({ locale })`), then `.locale()` — and otherwise in the client's own (`withLocale()`,
1416
- * `updateConfig({ locale })`) as it is when the call is made, as `content()`'s are: reads, the write,
1417
- * and version history alike.
1418
- *
1419
- * @example
1420
- * ```typescript
1421
- * const homepage = await client.singleton('homepage')
1422
- * .include(['featuredProducts', 'heroImage'])
1423
- * .locale('en')
1424
- * .fetch()
1425
- * ```
1426
- */
1427
- declare class SingletonClient<T = unknown, R extends Reading = UntypedReading<T>, I extends string = never> {
1428
- private httpClient;
1429
- private singletonKey;
1430
- private includeFields?;
1431
- private localeValue?;
1432
- /** The client's locale, asked on every call rather than copied when this client was taken. */
1433
- private readonly clientLocale;
1434
- /**
1435
- * @param locale - The client's locale: the function `TypedRelyIOClient` hands over, asked on
1436
- * every call, or a locale fixed for this client
1437
- */
1438
- constructor(httpClient: HttpClient, singletonKey: string, locale?: ClientLocale);
1439
- /**
1440
- * Expand references into the entries they point at: one include path per element (REL-1801).
1441
- * A path names fields through lists, objects and references — `'hero.media.image'`,
1442
- * `'featured.author'` — and may pick the fields each entry returns, `'featured(title,slug)'`.
1443
- * Each is checked against the schema: a path that holds no reference does not compile, and the
1444
- * read's type follows the paths — an expanded reference is the entry, every other one its id. A
1445
- * second call adds to the first.
1446
- */
1447
- include<const P extends readonly string[]>(paths: CheckedIncludes<R, P>): SingletonClient<ReadWith<R, I | P[number]>, R, I | P[number]>;
1448
- /**
1449
- * Set locale for localized content
1450
- * @param locale - Locale code (e.g., 'en', 'de', 'fr')
1451
- */
1452
- locale(locale: string): this;
1453
- /**
1454
- * Fetch the singleton content
1455
- * @param options - Optional fetch options (alternative to fluent methods)
1456
- */
1457
- fetch<const P extends readonly string[]>(options: Omit<SingletonFetchOptions, 'include'> & {
1458
- include: CheckedIncludes<R, P>;
1459
- }): Promise<ReadWith<R, I | P[number]> | null>;
1460
- fetch(options?: Omit<SingletonFetchOptions, 'include'>): Promise<T | null>;
1461
- /**
1462
- * Fetch the singleton **and the entity tag that read served**, which is what
1463
- * `update`'s `ifMatch` takes (`INV-API-CONDITIONAL-WRITE-IS-REACHABLE`).
1464
- *
1465
- * `entityTag` is absent on a cache HIT: a cached body cannot know the current
1466
- * version, and a stale tag would refuse the very next write.
1467
- */
1468
- fetchWithTag(options?: SingletonFetchOptions): Promise<{
1469
- content: T;
1470
- entityTag?: string;
1471
- }>;
1472
- /**
1473
- * Update singleton content. The API auto-publishes on PATCH — there is no
1474
- * separate publish route; calling `update()` writes and publishes.
1475
- * @param data - The content fields to update
1476
- * @param locale - Optional locale for localized content (overrides the fluent locale, which
1477
- * overrides the client's own)
1478
- * @param options - `If-Match` / `Idempotency-Key`, both declared on this route
1479
- */
1480
- update(data: Partial<T>, locale?: string, options?: ConditionalWriteOptions): Promise<T>;
1481
- /**
1482
- * Check if singleton exists
1483
- */
1484
- exists(): Promise<boolean>;
1485
- /**
1486
- * Get version history for one locale of the singleton.
1487
- *
1488
- * Versions are numbered per locale, so this is one locale's timeline: `locale` defaults to the
1489
- * fluent `.locale()`, then the client's own (`withLocale()`), then the schema default; no
1490
- * fallback is applied, and a locale with no history returns an empty array rather than another
1491
- * locale's rows. Every row names the locale it belongs to.
1492
- */
1493
- versions(options?: {
1494
- limit?: number;
1495
- locale?: string;
1496
- }): Promise<Array<T & {
1497
- versionNumber: number;
1498
- locale: string;
1499
- }>>;
1500
- /**
1501
- * Get a specific version of the singleton.
1502
- *
1503
- * `locale` defaults to the fluent `.locale()`, then the client's own (`withLocale()`), then the
1504
- * schema default. Version numbers are per locale, so version 2 is different content in each one.
1505
- */
1506
- version(versionNumber: number, options?: {
1507
- locale?: string;
1508
- }): Promise<T | null>;
1509
- /** The locale a write or history read sends: the call's, the fluent one, else the client's. */
1510
- private localeOf;
1511
- /**
1512
- * Get the singleton key
1513
- */
1514
- getSingletonKey(): string;
1515
- /**
1516
- * Reset fluent options for reuse
1517
- */
1518
- reset(): SingletonClient<ReadWith<R, never>, R>;
1519
- }
1520
-
1521
- /**
1522
- * Settings fetch options
1523
- */
1524
- interface SettingsFetchOptions {
1525
- /** Locale for localized settings */
1526
- locale?: string;
1527
- /**
1528
- * Include paths: the references to expand (REL-1801), added to the ones `include()` named — see
1529
- * `include()`. Typed where the call names them inline.
1530
- */
1531
- include?: readonly string[];
1532
- }
1533
- /**
1534
- * Settings client for managing settings group operations
1535
- * Provides a fluent API for settings operations following the same pattern as ContentClient
1536
- *
1537
- * Every call is in the locale it names — the argument (`fetch({ locale })`, `update(settings,
1538
- * locale)`, `reset(locale)`, `versions({ locale })`), then `.locale()` — and otherwise in the
1539
- * client's own (`withLocale()`, `updateConfig({ locale })`) as it is when the call is made, as
1540
- * `content()`'s are: reads, writes and version history alike.
1541
- *
1542
- * @example
1543
- * ```typescript
1544
- * const emailConfig = await client.settings('email-config')
1545
- * .include(['defaultTemplate'])
1546
- * .locale('en')
1547
- * .fetch()
1548
- * ```
1549
- */
1550
- declare class SettingsClient<T = unknown, R extends Reading = UntypedReading<T>, I extends string = never> {
1551
- private readonly httpClient;
1552
- private readonly groupId;
1553
- private includeFields?;
1554
- private localeValue?;
1555
- /** The client's locale, asked on every call rather than copied when this client was taken. */
1556
- private readonly clientLocale;
1557
- /**
1558
- * @param locale - The client's locale: the function `TypedRelyIOClient` hands over, asked on
1559
- * every call, or a locale fixed for this client
1560
- */
1561
- constructor(httpClient: HttpClient, groupId: string, locale?: ClientLocale);
1562
- /**
1563
- * Expand references into the entries they point at: one include path per element (REL-1801).
1564
- * A path names fields through lists, objects and references — `'hero.media.image'`,
1565
- * `'featured.author'` — and may pick the fields each entry returns, `'featured(title,slug)'`.
1566
- * Each is checked against the schema: a path that holds no reference does not compile, and the
1567
- * read's type follows the paths — an expanded reference is the entry, every other one its id. A
1568
- * second call adds to the first.
1569
- */
1570
- include<const P extends readonly string[]>(paths: CheckedIncludes<R, P>): SettingsClient<ReadWith<R, I | P[number]>, R, I | P[number]>;
1571
- /**
1572
- * Set locale for localized settings
1573
- * @param locale - Locale code (e.g., 'en', 'de', 'fr')
1574
- */
1575
- locale(locale: string): this;
1576
- /**
1577
- * Fetch the settings group
1578
- * @param options - Optional fetch options (alternative to fluent methods)
1579
- */
1580
- fetch<const P extends readonly string[]>(options: Omit<SettingsFetchOptions, 'include'> & {
1581
- include: CheckedIncludes<R, P>;
1582
- }): Promise<ReadWith<R, I | P[number]> | null>;
1583
- fetch(options?: Omit<SettingsFetchOptions, 'include'>): Promise<T | null>;
1584
- /**
1585
- * Update settings group
1586
- *
1587
- * The fields travel as `content` beside an optional `locale`, the body the route validates
1588
- * (`UpdateSettingsDto`); any other top-level key is refused `REQUEST_INVALID`.
1589
- *
1590
- * @param settings - The settings fields to update
1591
- * @param locale - Optional locale for localized settings (overrides the fluent locale, which
1592
- * overrides the client's own)
1593
- */
1594
- update(settings: Partial<T>, locale?: string, options?: ConditionalWriteOptions): Promise<T>;
1595
- /**
1596
- * Fetch the group **and the entity tag that read served**, which is what
1597
- * `update`'s and `reset`'s `ifMatch` take
1598
- * (`INV-API-CONDITIONAL-WRITE-IS-REACHABLE`).
1599
- */
1600
- fetchWithTag(options?: SettingsFetchOptions): Promise<{
1601
- settings: T;
1602
- entityTag?: string;
1603
- }>;
1604
- /**
1605
- * Reset settings to default values
1606
- *
1607
- * The route reads its locale from `?locale=` and takes no body, so that is where it is sent.
1608
- *
1609
- * @param locale - Optional locale for localized settings (overrides the fluent locale, which
1610
- * overrides the client's own)
1611
- */
1612
- reset(locale?: string, options?: ConditionalWriteOptions): Promise<T>;
1613
- /**
1614
- * Get default settings for this group
1615
- */
1616
- getDefaults(): Promise<T>;
1617
- /**
1618
- * Check if settings group exists
1619
- */
1620
- exists(): Promise<boolean>;
1621
- /**
1622
- * Get version history for one locale of the settings group.
1623
- *
1624
- * Versions are numbered per locale, so this is one locale's timeline: `locale` defaults to the
1625
- * fluent `.locale()`, then the client's own (`withLocale()`), then the schema default; no
1626
- * fallback is applied, and a locale with no history returns an empty array rather than another
1627
- * locale's rows. Every row names the locale it belongs to.
1628
- */
1629
- versions(options?: {
1630
- limit?: number;
1631
- locale?: string;
1632
- }): Promise<Array<T & {
1633
- versionNumber: number;
1634
- locale: string;
1635
- }>>;
1636
- /**
1637
- * Get a specific version of the settings.
1638
- *
1639
- * `locale` defaults to the fluent `.locale()`, then the client's own (`withLocale()`), then the
1640
- * schema default. Version numbers are per locale, so version 2 is different content in each one.
1641
- */
1642
- version(versionNumber: number, options?: {
1643
- locale?: string;
1644
- }): Promise<T | null>;
1645
- /** The locale a write or history read sends: the call's, the fluent one, else the client's. */
1646
- private localeOf;
1647
- /**
1648
- * Get the settings group ID
1649
- */
1650
- getGroupId(): string;
1651
- /**
1652
- * Reset fluent options for reuse
1653
- */
1654
- resetOptions(): SettingsClient<ReadWith<R, never>, R>;
1655
- }
1656
-
1657
- /**
1658
- * Any schema the client can be typed by. `any` is deliberate: the client accepts whatever content
1659
- * types, singletons, queries and settings groups a customer declares.
1660
- *
1661
- * This is the constraint `TypedRelyIOClient`'s `TSchema` carries, so `RegisteredSchema` is inferred
1662
- * against the same one — `createClient` returns `TypedRelyIOClient<RegisteredSchema>`, and a schema
1663
- * inferred against any wider alias would not be provably inside the client's bound.
1664
- */
1665
- type AnySchemaDefinition = SchemaDefinition<any, any, any, any, any>;
1666
- /**
1667
- * The seam `relyio generate-types` fills (`INV-SS-TYPES-INFERRED`).
1668
- *
1669
- * Empty here on purpose. The generated `schema.ts` augments it —
1670
- *
1671
- * ```ts
1672
- * declare module '@relyio/sdk' {
1673
- * interface Register {
1674
- * schema: typeof schema
1675
- * }
1676
- * }
1677
- * ```
1678
- *
1679
- * — and from then on `createClient({ apiKey })`, with no generic and no `schema:`, is typed by that
1680
- * schema. The pattern is TanStack Router's. It is type-level only: nothing at runtime reads it.
1681
- */
1682
- interface Register {
1683
- }
1684
- /**
1685
- * The schema the project registered through `Register`, or `undefined` when nothing did — which is
1686
- * the untyped client, exactly as before the generator ran.
1687
- */
1688
- type RegisteredSchema = Register extends {
1689
- schema: infer S extends AnySchemaDefinition;
1690
- } ? S : undefined;
1691
-
1692
- /**
1693
- * Search mode for content queries
1694
- */
1695
- type SearchMode = 'keyword' | 'semantic' | 'hybrid';
1696
- /**
1697
- * What a search or similar hit reports beside its fields: the committed document's
1698
- * `SearchHitMetadata`, read through `http/api-schema.ts`.
1699
- *
1700
- * Narrower than an entry read's `EntryMetadata`, because a hit is not built by the entry read: it
1701
- * carries no `entryType`, `defaultLocale`, `availableLocales`, `refs`, `firstPublishedAt` or
1702
- * version history. Until REL-1413 a hit was typed as an entry and so promised all five — the
1703
- * locale helpers kept an optional chain after `availableLocales` for the hits that lacked it. Read
1704
- * the entry (`content(type).findById(hit._metadata.id)`) for those.
1705
- */
1706
- type SearchHitMetadata = components['schemas']['SearchHitMetadata'];
1707
- /** What a hit carries beside the entry's fields, as the document declares it. */
1708
- type SearchHitSystemFields = Pick<components['schemas']['SearchHit'], '_metadata' | '_score' | '_scoreBreakdown' | '_highlights'>;
1709
- /**
1710
- * One search hit as the route emits it — the document's `SearchHit`: the fields `T` declares at the
1711
- * top level (at `_metadata.locale`, or only the aliases a `fields` projection names), the hit's own
1712
- * `_metadata`, and the score annotations the request asked for. There is no per-item `data` wrapper.
1713
- *
1714
- * `T`'s `_metadata` is replaced, not kept: `T` is an entry type, and a hit is not an entry read.
1715
- * Every field is optional, a required one too: a `fields` projection names only some of them, and a
1716
- * hit is built from the search row, not by the read that fills a required field a draft holds empty
1717
- * (REL-1795). Distributes over a union of entry types, so a search across two types keeps each
1718
- * one's fields.
1719
- */
1720
- type SearchResultItem<T = unknown> = T extends unknown ? Partial<Omit<T, '_metadata'>> & SearchHitSystemFields : never;
1721
- /**
1722
- * Search response from API
1723
- */
1724
- /**
1725
- * What `POST /v1/search` serves, **read from the committed document**.
1726
- *
1727
- * `{ data, page: { offset, limit, total }, meta }` — search is the offset
1728
- * dialect precisely because a facet-bearing result wants a total and an offset
1729
- * (Decision 5), and the facets, the strategy the engine chose and its warnings
1730
- * ride the declared `meta` sibling because none of them is an item or a page
1731
- * fact.
1732
- *
1733
- * The hand-written `{ items, total?, metadata? }` this replaces named three
1734
- * keys the route does not send. `SearchBuilder.items()` returned `undefined`
1735
- * and `.total` was always `undefined` — with nothing able to see either,
1736
- * because the type asserted them.
1737
- */
1738
- type SearchResponse<T = unknown> = Wire<'/v1/search', 'post', SearchResultItem<T>>;
1739
- /**
1740
- * One autocomplete suggestion — the document's `AutocompleteSuggestion`: the value, its best score,
1741
- * an entry holding it (`id`), and the value with its match wrapped in `<em>` when Atlas highlighted
1742
- * it. The hand-written type this replaces named `highlighted`, `entryId` and `contentType`, none of
1743
- * which the route sends, and made `score` optional where it is always sent.
1744
- */
1745
- type AutocompleteSuggestion = components['schemas']['AutocompleteSuggestion'];
1746
- /**
1747
- * Autocomplete response
1748
- */
1749
- /**
1750
- * What `POST /v1/search/autocomplete` serves. The suggestions are the
1751
- * collection's `data`; the query it completed and the field it matched on ride
1752
- * `meta`. Autocomplete is unpaginated, so its page reports `offset: 0` and a
1753
- * `total` equal to what it returned — the dialect is declared rather than
1754
- * omitted so one collection shape covers every list.
1755
- */
1756
- type AutocompleteResponse = Wire<'/v1/search/autocomplete', 'post', AutocompleteSuggestion>;
1757
- /**
1758
- * One similar-content hit as the route emits it: the same `SearchHit` a search serves, built by
1759
- * the same transform, with `_score` on every hit.
1760
- */
1761
- type SimilarResult<T = unknown> = SearchResultItem<T>;
1762
- /**
1763
- * Similar response
1764
- */
1765
- /** What `POST /v1/search/similar` serves. */
1766
- type SimilarResponse<T = unknown> = Wire<'/v1/search/similar', 'post', SimilarResult<T>>;
1767
- /**
1768
- * Filter conditions for search
1769
- */
1770
- type SearchFilter = Record<string, unknown>;
1771
- /**
1772
- * The content type names a search may be scoped to: the schema's where one types the client, any
1773
- * name where none does.
1774
- */
1775
- type SearchTypeName<S extends AnySchemaDefinition | undefined> = S extends AnySchemaDefinition ? SchemaContentTypeNames<S> : string;
1776
- /**
1777
- * What a search over the content types `K` returns: their entries, typed by the schema
1778
- * (`INV-SS-TYPES-INFERRED`) — or `T` unchanged where no schema types the client.
1779
- */
1780
- type SearchEntry<S extends AnySchemaDefinition | undefined, K extends SearchTypeName<S> = SearchTypeName<S>, T extends BaseContent = BaseContent> = S extends AnySchemaDefinition ? SchemaEntryOf<S, K & SchemaContentTypeNames<S>> : T;
1781
- /**
1782
- * Field projection for search results
1783
- */
1784
- type SearchFields<T> = {
1785
- [K: string]: keyof T | string;
1786
- };
1787
- /**
1788
- * One ordering of `POST /v1/search`'s results, as the route's `sort` array takes it.
1789
- */
1790
- interface SearchSort {
1791
- /** Field path, or `_score` for relevance */
1792
- field: string;
1793
- /** Direction; the route's default is `desc` */
1794
- direction: 'asc' | 'desc';
1795
- }
1796
- /**
1797
- * Fluent builder for search queries
1798
- *
1799
- * @example
1800
- * ```typescript
1801
- * const results = await relyio.search('wireless headphones')
1802
- * .in('product')
1803
- * .where({ status: 'active' })
1804
- * .searchFields(['title', 'description'])
1805
- * .limit(20)
1806
- * .includeScore()
1807
- * .execute()
1808
- * ```
1809
- */
1810
- declare class SearchBuilder<T extends BaseContent = BaseContent, S extends AnySchemaDefinition | undefined = undefined> {
1811
- private readonly httpClient;
1812
- private query;
1813
- private searchOptions;
1814
- private requestHeaders?;
1815
- private freshValue?;
1816
- /** The client's locale, asked when the query is sent rather than copied when it was built. */
1817
- private readonly clientLocale;
1818
- /**
1819
- * @param locale - The client's locale (`withLocale()`, `updateConfig({ locale })`), sent when
1820
- * `.locale()` names none: the function `TypedRelyIOClient` hands over, or a locale fixed for it
1821
- */
1822
- constructor(httpClient: HttpClient, query: string, locale?: ClientLocale);
1823
- /**
1824
- * Headers this search adds to the client's own, e.g. `X-RelyIO-Dataset` to search another
1825
- * dataset the key can see. A name the client already sends is replaced for this search only.
1826
- */
1827
- headers(headers: Record<string, string>): this;
1828
- /**
1829
- * The feed position this search follows — a change's `fresh` (`FeedChange`), or a resync's. Sent
1830
- * as `?_fresh` on the query string beside the body: the edge keys a search on both, so no answer
1831
- * it holds from before that position can answer it. The answer is the search's own.
1832
- */
1833
- fresh(position: string): this;
1834
- /**
1835
- * Search within a single content type. Where a schema types the client, the name is one of its
1836
- * content types and the results are that type's entries.
1837
- * @param contentType - Content type to search in
1838
- */
1839
- in<K extends SearchTypeName<S>>(contentType: K): SearchBuilder<SearchEntry<S, K, T>, S>;
1840
- /**
1841
- * Search across multiple content types. Where a schema types the client, the names are its content
1842
- * types and the results are a union of their entries — every type's, for `'*'`.
1843
- * @param contentTypes - Array of content types or '*' for all
1844
- */
1845
- across<K extends SearchTypeName<S>>(contentTypes: K[] | '*'): SearchBuilder<SearchEntry<S, K, T>, S>;
1846
- /**
1847
- * Add filter conditions, on the fields the content types declare. A condition on `_metadata` is
1848
- * refused here, before anything is sent.
1849
- * @param conditions - Filter conditions
1850
- */
1851
- where(conditions: SearchFilter): this;
1852
- /**
1853
- * Specify which fields to search in
1854
- * @param fields - Field names to search
1855
- */
1856
- searchFields(fields: string[]): this;
1857
- /**
1858
- * Project specific fields in results
1859
- * @param projection - Field mapping
1860
- */
1861
- fields(projection: Record<string, string>): this;
1862
- /**
1863
- * Order the results by a field instead of by relevance. Called again, it adds a tie-breaker.
1864
- * `_score` names relevance itself.
1865
- * @param field - Field path to sort by (e.g. `updatedAt`, `pricing.basePrice`)
1866
- * @param direction - `asc` or `desc` (the route's default is `desc`)
1867
- */
1868
- sort(field: string, direction?: 'asc' | 'desc'): this;
1869
- /**
1870
- * Limit number of results
1871
- * @param count - Maximum results
1872
- */
1873
- limit(count: number): this;
1874
- /**
1875
- * Skip results for pagination
1876
- * @param count - Number to skip
1877
- */
1878
- offset(count: number): this;
1879
- /**
1880
- * Include relevance scores in results
1881
- */
1882
- includeScore(): this;
1883
- /**
1884
- * Include score breakdown explanation
1885
- */
1886
- explain(): this;
1887
- /**
1888
- * Set locale for localized content
1889
- * @param locale - Locale code
1890
- */
1891
- locale(locale: string): this;
1892
- /**
1893
- * Execute the search query
1894
- */
1895
- execute(): Promise<SearchResponse<T>>;
1896
- /**
1897
- * Execute and return just the items (convenience method)
1898
- */
1899
- items(): Promise<Array<SearchResultItem<T>>>;
1900
- /**
1901
- * Get the first result
1902
- */
1903
- first(): Promise<SearchResultItem<T> | null>;
1904
- }
1905
- /**
1906
- * Fluent builder for autocomplete queries
1907
- *
1908
- * @example
1909
- * ```typescript
1910
- * const suggestions = await relyio.autocomplete('wire')
1911
- * .in('product')
1912
- * .field('title')
1913
- * .fuzzy()
1914
- * .limit(10)
1915
- * .execute()
1916
- * ```
1917
- */
1918
- declare class AutocompleteBuilder {
1919
- private readonly httpClient;
1920
- private query;
1921
- private autocompleteOptions;
1922
- /** The client's locale, asked when the query is sent rather than copied when it was built. */
1923
- private readonly clientLocale;
1924
- /**
1925
- * @param locale - The client's locale (`withLocale()`, `updateConfig({ locale })`), sent when
1926
- * `.locale()` names none: the function `TypedRelyIOClient` hands over, or a locale fixed for it
1927
- */
1928
- constructor(httpClient: HttpClient, query: string, locale?: ClientLocale);
1929
- /**
1930
- * Autocomplete within a content type
1931
- * @param contentType - Content type to search
1932
- */
1933
- in(contentType: string): this;
1934
- /**
1935
- * Field to autocomplete on
1936
- * @param fieldName - Field name (supports nested like 'category.name')
1937
- */
1938
- field(fieldName: string): this;
1939
- /**
1940
- * Enable fuzzy matching for typo tolerance
1941
- */
1942
- fuzzy(): this;
1943
- /**
1944
- * Add filter conditions, on the fields the content types declare. A condition on `_metadata` is
1945
- * refused here, before anything is sent.
1946
- * @param conditions - Filter conditions
1947
- */
1948
- where(conditions: SearchFilter): this;
1949
- /**
1950
- * Limit number of suggestions
1951
- * @param count - Maximum suggestions
1952
- */
1953
- limit(count: number): this;
1954
- /**
1955
- * Set locale for localized content
1956
- * @param locale - Locale code
1957
- */
1958
- locale(locale: string): this;
1959
- /**
1960
- * Execute the autocomplete query
1961
- */
1962
- execute(): Promise<AutocompleteResponse>;
1963
- /**
1964
- * Get just the suggestion texts
1965
- */
1966
- texts(): Promise<string[]>;
1967
- }
1968
- /**
1969
- * Fluent builder for finding similar content
1970
- *
1971
- * @example
1972
- * ```typescript
1973
- * const similar = await relyio.similar('entry_abc123')
1974
- * .in('product')
1975
- * .locale('fr-FR')
1976
- * .limit(5)
1977
- * .execute()
1978
- * ```
1979
- */
1980
- declare class SimilarBuilder<T extends BaseContent = BaseContent> {
1981
- private readonly httpClient;
1982
- private sourceId;
1983
- private similarOptions;
1984
- private requestHeaders?;
1985
- /** The client's locale, asked when the read is sent rather than copied when it was built. */
1986
- private readonly clientLocale;
1987
- /**
1988
- * @param locale - The client's locale (`withLocale()`, `updateConfig({ locale })`), sent when
1989
- * `.locale()` names none: the function `TypedRelyIOClient` hands over, or a locale fixed for it
1990
- */
1991
- constructor(httpClient: HttpClient, sourceId: string, locale?: ClientLocale);
1992
- /**
1993
- * Headers this read adds to the client's own, e.g. `X-RelyIO-Dataset` to read another dataset
1994
- * the key can see. A name the client already sends is replaced for this read only.
1995
- */
1996
- headers(headers: Record<string, string>): this;
1997
- /**
1998
- * Find similar content within a content type
1999
- * @param contentType - Content type to search
2000
- */
2001
- in(contentType: string): this;
2002
- /**
2003
- * Find similar content across several content types, or `'*'` for every type
2004
- * @param contentTypes - Array of content types or '*' for all
2005
- */
2006
- across(contentTypes: string[] | '*'): this;
2007
- /**
2008
- * Limit number of results
2009
- * @param count - Maximum results
2010
- */
2011
- limit(count: number): this;
2012
- /**
2013
- * Set locale for localized content
2014
- * @param locale - Locale code
2015
- */
2016
- locale(locale: string): this;
2017
- /**
2018
- * Execute the similar query
2019
- */
2020
- execute(): Promise<SimilarResponse<T>>;
2021
- /**
2022
- * Get just the similar items
2023
- */
2024
- items(): Promise<Array<SimilarResult<T>>>;
2025
- /**
2026
- * Get the most similar item
2027
- */
2028
- first(): Promise<SimilarResult<T> | null>;
2029
- }
2030
- /**
2031
- * Search client providing fluent access to all search capabilities
2032
- *
2033
- * @example
2034
- * ```typescript
2035
- * const searchClient = new SearchClient(httpClient)
2036
- *
2037
- * // Full-text/hybrid search
2038
- * const products = await searchClient.search('wireless')
2039
- * .in('product')
2040
- * .limit(20)
2041
- * .execute()
2042
- *
2043
- * // Autocomplete
2044
- * const suggestions = await searchClient.autocomplete('wire')
2045
- * .in('product')
2046
- * .field('title')
2047
- * .execute()
2048
- *
2049
- * // Find similar
2050
- * const similar = await searchClient.similar('entry_123')
2051
- * .in('product')
2052
- * .execute()
2053
- * ```
2054
- */
2055
- declare class SearchClient {
2056
- private readonly httpClient;
2057
- /** The client's locale, handed to every builder here to ask when it sends. */
2058
- private readonly clientLocale;
2059
- /**
2060
- * @param locale - The client's locale (`withLocale()`, `updateConfig({ locale })`): every builder
2061
- * here sends it when the query names none, as `content()`'s reads do — the function
2062
- * `TypedRelyIOClient` hands over, or a locale fixed for this client
2063
- */
2064
- constructor(httpClient: HttpClient, locale?: ClientLocale);
2065
- /**
2066
- * Start a search query
2067
- * @param query - Search query string
2068
- */
2069
- search<T extends BaseContent = BaseContent, S extends AnySchemaDefinition | undefined = undefined>(query: string): SearchBuilder<T, S>;
2070
- /**
2071
- * Start an autocomplete query
2072
- * @param query - Partial query to autocomplete
2073
- */
2074
- autocomplete(query: string): AutocompleteBuilder;
2075
- /**
2076
- * Find content similar to a given entry
2077
- * @param sourceId - Entry ID to find similar content for
2078
- */
2079
- similar<T extends BaseContent = BaseContent>(sourceId: string): SimilarBuilder<T>;
2080
- }
2081
-
2082
- /**
2083
- * HTTP methods supported in batch operations
2084
- */
2085
- type BatchMethod = 'POST' | 'PATCH' | 'PUT' | 'DELETE';
2086
- /**
2087
- * A single batch operation
2088
- */
2089
- interface BatchOperation {
2090
- /** Optional operation ID for tracking */
2091
- id?: string;
2092
- /** HTTP method */
2093
- method: BatchMethod;
2094
- /** Path to resource (e.g., '/product' or '/product/entry_123') */
2095
- path: string;
2096
- /** Request body for create/update operations */
2097
- body?: Record<string, unknown>;
2098
- }
2099
- /**
2100
- * Batch execution options
2101
- */
2102
- interface BatchOptions {
2103
- /**
2104
- * If true, the batch runs as one transaction: every operation lands or none does.
2105
- *
2106
- * Requires that all operations be the same kind of write — one transaction cannot span two
2107
- * calls — so a batch mixing creates and deletes, or `update` and `replace`, is rejected with
2108
- * a 400 before anything is attempted: `execute()` rejects with `InvalidPayloadError`. A
2109
- * singleton write is a create when the dataset has never written that singleton and an update
2110
- * after, and an atomic batch cannot create one — that operation reports `REQUEST_INVALID` and
2111
- * every other `TRANSACTION_ROLLED_BACK` — so create it in a request of its own first.
2112
- *
2113
- * @default false
2114
- */
2115
- atomic?: boolean;
2116
- /**
2117
- * If true, run the checks and write nothing.
2118
- *
2119
- * Every entry is resolved, authorized and validated against its schema, so what passes a dry
2120
- * run is what a real write accepts.
2121
- *
2122
- * @default false
2123
- */
2124
- dryRun?: boolean;
2125
- /**
2126
- * If true, include full data in response
2127
- * @default true
2128
- */
2129
- returnData?: boolean;
2130
- /**
2131
- * If true, continue executing after failures (non-atomic mode only)
2132
- * @default true
2133
- */
2134
- continueOnError?: boolean;
2135
- }
2136
- /**
2137
- * Result of a single operation in the batch
2138
- */
2139
- /**
2140
- * One batch item, **as the committed document describes the `multi` kind**.
2141
- *
2142
- * Read from `api-schema.ts` rather than imported from `@relyio/api-contract`
2143
- * for the same reason every other response type here is: the document is the
2144
- * one source, and a type this package publishes must resolve for a customer who
2145
- * installs `@relyio/sdk` alone. The contract's runtime half is bundled; its
2146
- * `.d.ts` is not something a consumer can reach.
2147
- *
2148
- * The hand-written version carried `success: boolean` and a per-item `status`
2149
- * number. Both are gone from the wire and neither loss is a gap: the presence
2150
- * of `error` already says whether the item succeeded (Decision 2, gap 16), and
2151
- * a sub-response carrying its own HTTP status is exactly the duplication
2152
- * `INV-API-NO-DUPLICATE-STATUS` removes. What replaced them says more, not
2153
- * less — `outcome` distinguishes `created` from `updated` from `unchanged`
2154
- * from `duplicate` from `skipped`, which one boolean never could.
2155
- */
2156
- type BatchOperationResult = components['schemas']['MultiPayload']['results'][number];
2157
- /** Summary of batch execution, from the contract. */
2158
- type BatchSummary = components['schemas']['MultiSummary'];
2159
- /**
2160
- * The full batch payload: `{ results, summary }`.
2161
- *
2162
- * `transactionId`, `executionTimeMs` and `dryRun` were named here and are not
2163
- * on the wire; nothing produced them and nothing could read them.
2164
- */
2165
- type BatchResponse = components['schemas']['MultiPayload'];
2166
- /**
2167
- * Fluent builder for batch operations
2168
- *
2169
- * Every create, update, replace and singleton operation it builds is in the locale that operation
2170
- * names, and otherwise in the client's own (`withLocale()`), sent as that operation's
2171
- * `body.locale` — a batch is one request, but the API reads a locale per operation. A delete names
2172
- * none (it removes the whole entry), and a `raw()` operation is sent exactly as written.
2173
- *
2174
- * An atomic batch — `.atomic()` or `execute({ atomic: true })` — is one transaction: every
2175
- * operation lands or none does. The API accepts one only when every operation is the same kind of
2176
- * write and none writes a singleton that does not exist yet (see {@link BatchOptions.atomic}).
2177
- *
2178
- * @example
2179
- * ```typescript
2180
- * // Each operation lands or fails on its own
2181
- * const result = await relyio.batch()
2182
- * .create('product', { content: { title: 'iPhone', price: 999 } })
2183
- * .update('product', 'entry_abc', { content: { price: 899 } })
2184
- * .delete('product', 'entry_xyz')
2185
- * .upsertSingleton('homepage', { content: { hero: 'Welcome!' } })
2186
- * .execute()
2187
- *
2188
- * console.log(result.summary.succeeded)
2189
- *
2190
- * // All or nothing: one kind of write
2191
- * await relyio.batch()
2192
- * .create('product', { content: { title: 'iPhone', price: 999 } })
2193
- * .create('product', { content: { title: 'iPad', price: 799 } })
2194
- * .atomic()
2195
- * .execute()
2196
- * ```
2197
- */
2198
- declare class BatchBuilder<TSchema = unknown> {
2199
- private readonly httpClient;
2200
- private operations;
2201
- private defaultOptions;
2202
- private requestHeaders;
2203
- /** The client's locale, asked as each operation is added rather than copied when this was built. */
2204
- private readonly clientLocale;
2205
- /**
2206
- * @param locale - The client's locale (`withLocale()`, `updateConfig({ locale })`), each
2207
- * operation's when it names none: the function `TypedRelyIOClient` hands over, or a locale fixed
2208
- * for this batch
2209
- */
2210
- constructor(httpClient: HttpClient, locale?: ClientLocale);
2211
- /**
2212
- * An operation's body with its locale: the one it names, else the client's as it is when the
2213
- * operation is added.
2214
- */
2215
- private localized;
2216
- /**
2217
- * Add a create operation
2218
- * @param contentType - Content type to create
2219
- * @param data - Entry data with content field
2220
- * @param id - Optional operation ID for tracking
2221
- */
2222
- create<T extends BaseContent = BaseContent>(contentType: string, data: {
2223
- content: CreateInput<T>;
2224
- slug?: string;
2225
- locale?: string;
2226
- parentId?: string;
2227
- sortOrder?: number;
2228
- }, id?: string): this;
2229
- /**
2230
- * Add an update operation (partial update).
2231
- *
2232
- * The submitted fields are merged into the entry and the rest are left as they are. The merge is
2233
- * field-level rather than deep: sending an object-typed field replaces that field's whole value.
2234
- *
2235
- * @param contentType - Content type
2236
- * @param entryId - Entry ID to update
2237
- * @param data - Fields to update
2238
- * @param id - Optional operation ID for tracking
2239
- */
2240
- update<T extends BaseContent = BaseContent>(contentType: string, entryId: string, data: {
2241
- content?: UpdateInput<T>;
2242
- slug?: string;
2243
- locale?: string;
2244
- parentId?: string;
2245
- sortOrder?: number;
2246
- }, id?: string): this;
2247
- /**
2248
- * Add a replace operation (full replacement).
2249
- *
2250
- * The payload becomes the entry's content for the addressed locale: fields you do not send are
2251
- * cleared. Send the whole entry, or use {@link BatchClient.update} to change part of one.
2252
- *
2253
- * @param contentType - Content type
2254
- * @param entryId - Entry ID to replace
2255
- * @param data - Complete entry data
2256
- * @param id - Optional operation ID for tracking
2257
- */
2258
- replace<T extends BaseContent = BaseContent>(contentType: string, entryId: string, data: {
2259
- content: CreateInput<T>;
2260
- slug?: string;
2261
- locale?: string;
2262
- parentId?: string;
2263
- sortOrder?: number;
2264
- }, id?: string): this;
2265
- /**
2266
- * Add a delete operation
2267
- * @param contentType - Content type
2268
- * @param entryId - Entry ID to delete
2269
- * @param id - Optional operation ID for tracking
2270
- */
2271
- delete(contentType: string, entryId: string, id?: string): this;
2272
- /**
2273
- * Add a singleton upsert operation
2274
- * @param singletonKey - Singleton key
2275
- * @param data - Singleton content
2276
- * @param id - Optional operation ID for tracking
2277
- */
2278
- upsertSingleton(singletonKey: string, data: {
2279
- content: Record<string, unknown>;
2280
- locale?: string;
2281
- }, id?: string): this;
2282
- /**
2283
- * Add a singleton create operation (alias for upsert with POST)
2284
- * @param singletonKey - Singleton key
2285
- * @param data - Singleton content
2286
- * @param id - Optional operation ID for tracking
2287
- */
2288
- createSingleton(singletonKey: string, data: {
2289
- content: Record<string, unknown>;
2290
- locale?: string;
2291
- }, id?: string): this;
2292
- /**
2293
- * Add a raw operation (for advanced use cases). It is sent exactly as written: the client's
2294
- * locale is not added to it.
2295
- * @param operation - The operation to add
2296
- */
2297
- raw(operation: BatchOperation): this;
2298
- /**
2299
- * Add multiple raw operations
2300
- * @param operations - Operations to add
2301
- */
2302
- rawMany(operations: BatchOperation[]): this;
2303
- /**
2304
- * Bulk create multiple entries of the same content type
2305
- * @param contentType - Content type
2306
- * @param items - Array of entry data
2307
- */
2308
- bulkCreate<T extends BaseContent = BaseContent>(contentType: string, items: Array<{
2309
- content: CreateInput<T>;
2310
- slug?: string;
2311
- locale?: string;
2312
- id?: string;
2313
- }>): this;
2314
- /**
2315
- * Bulk update multiple entries
2316
- * @param contentType - Content type
2317
- * @param updates - Array of { entryId, data } pairs
2318
- */
2319
- bulkUpdate<T extends BaseContent = BaseContent>(contentType: string, updates: Array<{
2320
- entryId: string;
2321
- data: {
2322
- content?: UpdateInput<T>;
2323
- };
2324
- id?: string;
2325
- }>): this;
2326
- /**
2327
- * Bulk delete multiple entries
2328
- * @param contentType - Content type
2329
- * @param entryIds - Entry IDs to delete
2330
- */
2331
- bulkDelete(contentType: string, entryIds: string[]): this;
2332
- /**
2333
- * Configure default options for execution
2334
- * @param options - Batch options
2335
- */
2336
- configure(options: BatchOptions): this;
2337
- /**
2338
- * Enable atomic mode (all-or-nothing)
2339
- */
2340
- atomic(): this;
2341
- /**
2342
- * Enable dry-run mode (validate without executing)
2343
- */
2344
- dryRun(): this;
2345
- /**
2346
- * Disable returning full data in response
2347
- */
2348
- noData(): this;
2349
- /**
2350
- * Stop on first error (non-atomic mode)
2351
- */
2352
- stopOnError(): this;
2353
- /**
2354
- * Headers the batch request adds — e.g. `{ 'X-RelyIO-Dataset': 'staging' }` to write a dataset
2355
- * other than the client's. They replace the client's own header of the same name for this
2356
- * request alone, and apply to every operation in it: a batch is one request.
2357
- */
2358
- headers(headers: Record<string, string>): this;
2359
- /**
2360
- * Get the current list of operations
2361
- */
2362
- getOperations(): BatchOperation[];
2363
- /**
2364
- * Get the number of operations
2365
- */
2366
- size(): number;
2367
- /**
2368
- * Clear all operations
2369
- */
2370
- clear(): this;
2371
- /**
2372
- * Clone the builder
2373
- */
2374
- clone(): BatchBuilder<TSchema>;
2375
- /**
2376
- * Execute the batch operations
2377
- * @param options - Override default options
2378
- */
2379
- execute(options?: BatchOptions): Promise<BatchResponse>;
2380
- /**
2381
- * Validate operations without executing (convenience for dry-run)
2382
- */
2383
- validate(): Promise<BatchResponse>;
2384
- /**
2385
- * Execute and throw if any operation failed
2386
- */
2387
- executeOrThrow(options?: BatchOptions): Promise<BatchResponse>;
2388
- }
2389
- /**
2390
- * Batch client for bulk operations
2391
- *
2392
- * @example
2393
- * ```typescript
2394
- * const batchClient = new BatchClient(httpClient)
2395
- *
2396
- * // Create a batch with fluent API
2397
- * const result = await batchClient.create()
2398
- * .create('product', { content: { title: 'New Product' } })
2399
- * .update('product', 'entry_123', { content: { price: 99 } })
2400
- * .execute()
2401
- *
2402
- * // Quick bulk create
2403
- * const products = await batchClient.bulkCreate('product', [
2404
- * { content: { title: 'Product 1' } },
2405
- * { content: { title: 'Product 2' } },
2406
- * ])
2407
- * ```
2408
- */
2409
- declare class BatchClient<TSchema = unknown> {
2410
- private readonly httpClient;
2411
- /** The client's locale, handed to every batch this builds to ask as operations are added. */
2412
- private readonly clientLocale;
2413
- /**
2414
- * @param locale - The client's locale: the function `TypedRelyIOClient` hands over, or a locale
2415
- * fixed for this client
2416
- */
2417
- constructor(httpClient: HttpClient, locale?: ClientLocale);
2418
- /**
2419
- * Create a new batch builder, whose operations default to this client's locale
2420
- */
2421
- create(): BatchBuilder<TSchema>;
2422
- /**
2423
- * Quick bulk create entries
2424
- * @param contentType - Content type
2425
- * @param items - Array of entry data
2426
- * @param options - Batch options
2427
- */
2428
- bulkCreate<T extends BaseContent = BaseContent>(contentType: string, items: Array<{
2429
- content: CreateInput<T>;
2430
- slug?: string;
2431
- locale?: string;
2432
- }>, options?: BatchOptions): Promise<BatchResponse>;
2433
- /**
2434
- * Quick bulk update entries
2435
- * @param contentType - Content type
2436
- * @param updates - Array of updates
2437
- * @param options - Batch options
2438
- */
2439
- bulkUpdate<T extends BaseContent = BaseContent>(contentType: string, updates: Array<{
2440
- entryId: string;
2441
- data: {
2442
- content?: UpdateInput<T>;
2443
- };
2444
- }>, options?: BatchOptions): Promise<BatchResponse>;
2445
- /**
2446
- * Quick bulk delete entries
2447
- * @param contentType - Content type
2448
- * @param entryIds - Entry IDs to delete
2449
- * @param options - Batch options
2450
- */
2451
- bulkDelete(contentType: string, entryIds: string[], options?: BatchOptions): Promise<BatchResponse>;
2452
- /**
2453
- * Execute raw batch operations
2454
- * @param operations - Array of operations
2455
- * @param options - Batch options
2456
- */
2457
- execute(operations: BatchOperation[], options?: BatchOptions): Promise<BatchResponse>;
2458
- }
2459
-
2460
- /**
2461
- * Fetch block for retrieving content
2462
- */
2463
- interface FetchBlock {
2464
- /** Content type to fetch (mutually exclusive with singleton) */
2465
- contentType?: string;
2466
- /** Singleton key to fetch (mutually exclusive with contentType) */
2467
- singleton?: string;
2468
- /** Filter conditions */
2469
- where?: Record<string, unknown>;
2470
- /** Reference fields to populate */
2471
- include?: string | string[];
2472
- /** Sort specification */
2473
- sort?: Record<string, 'asc' | 'desc'>;
2474
- /** Maximum entries to return */
2475
- limit?: number;
2476
- /** Entries to skip */
2477
- offset?: number;
2478
- }
2479
- /**
2480
- * Aggregate block for computing metrics
2481
- */
2482
- interface AggregateBlock {
2483
- /** Source content type or query reference */
2484
- source: string;
2485
- /**
2486
- * Filter conditions applied before grouping, in the Query DSL's spelling — bare operator names
2487
- * (`{ price: { gte: 10 } }`), not the list route's `$gte`. A `$`-prefixed key is not an operator
2488
- * in this dialect: the service reads the object as a nested field and drops it.
2489
- */
2490
- where?: Record<string, unknown>;
2491
- /** Field to group by */
2492
- by?: string | string[];
2493
- /** Metrics to compute, keyed by the name each one answers under */
2494
- metrics: Record<string, AggregateMetric>;
2495
- }
2496
- /**
2497
- * One metric, as the Query DSL writes it: `<field>.<fn>`, where `fn` is `count`, `sum`, `avg`,
2498
- * `min`, `max`, `first` or `last` — `'id.count'`, `'pricing.basePrice.avg'`. The service
2499
- * validates every metric against that form (`INVALID_AGGREGATION_EXPRESSION`); `count` ignores the
2500
- * field it names. `first` and `last` were missing here while the service ran them (REL-1470).
2501
- *
2502
- * It used to be `{ fn, field? }`, a shape the service refuses on every request: the validator
2503
- * tests the metric as a string, so an object arrives as `[object Object]`.
2504
- */
2505
- type AggregateMetric = `${string}.${'count' | 'sum' | 'avg' | 'min' | 'max' | 'first' | 'last'}`;
2506
- /**
2507
- * Search block for full-text/semantic search
2508
- */
2509
- interface SearchBlock {
2510
- /** Content type to search */
2511
- contentType: string;
2512
- /** Search query (can use ${param} syntax) */
2513
- query: string;
2514
- /** Search mode */
2515
- mode?: 'keyword' | 'semantic' | 'hybrid';
2516
- /** Fields to search */
2517
- searchFields?: string[];
2518
- /** Additional filters */
2519
- where?: Record<string, unknown>;
2520
- /** Maximum results */
2521
- limit?: number;
2522
- }
2523
- /**
2524
- * One post-fetch filter: a parameter expression, or the operators every query clause compares with
2525
- * (`FilterOperator`, REL-1479). This kept a copy of eleven, without `exists` or `notExists`; and a
2526
- * bare number or boolean, which it took, the service does not read as a filter.
2527
- */
2528
- type FilterValue = string | FilterOperator;
2529
- /**
2530
- * Full-text search configuration
2531
- */
2532
- interface FullTextConfig {
2533
- /** Search query (can use ${param} syntax) */
2534
- query: string;
2535
- /** Fields to search */
2536
- fields?: string[];
2537
- /** Minimum score threshold */
2538
- minScore?: number;
2539
- }
2540
- /**
2541
- * Ad-hoc query definition
2542
- */
2543
- interface AdhocQueryDefinition {
2544
- /** Fetch block - retrieve content */
2545
- fetch?: FetchBlock;
2546
- /** Aggregate block - compute metrics */
2547
- aggregate?: AggregateBlock;
2548
- /** Search block - full-text/semantic search */
2549
- search?: SearchBlock;
2550
- /** Field selection/transformation */
2551
- select?: Record<string, string>;
2552
- /** Post-fetch filters with parameter support */
2553
- filters?: Record<string, FilterValue>;
2554
- /** Full-text search configuration */
2555
- fullText?: FullTextConfig;
2556
- /** Cache policy */
2557
- cache?: {
2558
- ttl?: number;
2559
- strategy?: 'stale-while-revalidate' | 'cache-first' | 'network-first';
2560
- };
2561
- }
2562
- /**
2563
- * Ad-hoc query execution options
2564
- */
2565
- interface AdhocQueryOptions {
2566
- /** Include debug information in response */
2567
- debug?: boolean;
2568
- /** Bypass cache for this request */
2569
- skipCache?: boolean;
2570
- /** Custom timeout in milliseconds (1000-60000) */
2571
- timeout?: number;
2572
- }
2573
- /**
2574
- * Query execution metadata
2575
- */
2576
- interface QueryMetadata {
2577
- /** Query identifier */
2578
- queryId: string;
2579
- /** Execution time in milliseconds */
2580
- executionTimeMs: number;
2581
- /** Number of results */
2582
- resultCount: number;
2583
- /** ISO timestamp */
2584
- timestamp: string;
2585
- /** Whether result was from cache */
2586
- cacheHit?: boolean;
2587
- /** Generated pipeline (if debug enabled) */
2588
- pipeline?: unknown[];
2589
- /** Resolved parameters (if debug enabled) */
2590
- parameters?: Record<string, unknown>;
2591
- /** Validation warnings */
2592
- warnings?: string[];
2593
- }
2594
- /**
2595
- * The payload `POST /v1/query` serves: `{ result, metadata }`.
2596
- *
2597
- * The route is an `object` kind, so there is one payload key whether the query
2598
- * fetched a list or a singleton — `result` is the records either way. Three
2599
- * members of the hand-written type are gone and each for a stated reason:
2600
- * `success` because the status line says it, `items` because no contract shape
2601
- * introduces that key (`INV-API-SUCCESS-HAS-ONE-SHAPE`), and `errors` because a
2602
- * failed query is a **refusal**, which arrives as `{ error: { code, messages } }`
2603
- * and is thrown as a typed SDK error rather than returned as a 200 with a flag.
2604
- */
2605
- interface AdhocQueryResult<T = unknown> {
2606
- /** The records the query produced — an array for a list, one item for a singleton. */
2607
- result?: T | T[];
2608
- /** Execution metadata */
2609
- metadata?: QueryMetadata;
2610
- }
2611
- /**
2612
- * Fluent builder for ad-hoc queries
2613
- *
2614
- * @example
2615
- * ```typescript
2616
- * const result = await relyio.query.adhoc()
2617
- * .fetch({
2618
- * contentType: 'product',
2619
- * where: { status: 'active' },
2620
- * include: ['brand', 'categories'],
2621
- * limit: 20
2622
- * })
2623
- * .select({
2624
- * id: 'id',
2625
- * title: 'title',
2626
- * price: 'pricing.basePrice',
2627
- * brandName: 'brand.name'
2628
- * })
2629
- * .filter('pricing.basePrice', { gte: '${minPrice}', lte: '${maxPrice}' })
2630
- * .params({ minPrice: 100, maxPrice: 500 })
2631
- * .debug()
2632
- * .execute()
2633
- * ```
2634
- */
2635
- declare class AdhocQueryBuilder<T = unknown> {
2636
- private readonly httpClient;
2637
- private queryDef;
2638
- private parameters;
2639
- private options;
2640
- /** The client's locale, asked when the query is sent rather than copied when it was built. */
2641
- private readonly clientLocale;
2642
- /**
2643
- * @param locale - The client's locale (`withLocale()`, `updateConfig({ locale })`), sent as the
2644
- * `locale` parameter when `.param('locale', …)` binds none: the function `TypedRelyIOClient`
2645
- * hands over, or a locale fixed for this builder
2646
- */
2647
- constructor(httpClient: HttpClient, locale?: ClientLocale);
2648
- /**
2649
- * Add a fetch block to retrieve content
2650
- * @param config - Fetch configuration
2651
- */
2652
- fetch(config: FetchBlock): this;
2653
- /**
2654
- * Fetch from a content type
2655
- * @param contentType - Content type to fetch
2656
- */
2657
- from(contentType: string): this;
2658
- /**
2659
- * Fetch a singleton
2660
- * @param singletonKey - Singleton key
2661
- */
2662
- singleton(singletonKey: string): this;
2663
- /**
2664
- * Add aggregate block
2665
- * @param config - Aggregate configuration
2666
- */
2667
- aggregate(config: AggregateBlock): this;
2668
- /**
2669
- * Add search block
2670
- * @param config - Search configuration
2671
- */
2672
- search(config: SearchBlock): this;
2673
- /**
2674
- * Add where conditions to fetch
2675
- * @param conditions - Filter conditions
2676
- */
2677
- where(conditions: Record<string, unknown>): this;
2678
- /**
2679
- * Add include for reference population
2680
- * @param fields - Reference fields to include
2681
- */
2682
- include(fields: string | string[]): this;
2683
- /**
2684
- * Add sort specification
2685
- * @param sort - Sort configuration
2686
- */
2687
- sort(sort: Record<string, 'asc' | 'desc'>): this;
2688
- /**
2689
- * Sort by field ascending
2690
- * @param field - Field to sort by
2691
- */
2692
- sortAsc(field: string): this;
2693
- /**
2694
- * Sort by field descending
2695
- * @param field - Field to sort by
2696
- */
2697
- sortDesc(field: string): this;
2698
- /**
2699
- * Limit results
2700
- * @param count - Maximum results
2701
- */
2702
- limit(count: number): this;
2703
- /**
2704
- * Skip results
2705
- * @param count - Number to skip
2706
- */
2707
- offset(count: number): this;
2708
- /**
2709
- * Select and transform fields in output
2710
- * @param projection - Field mapping (outputField: sourceField)
2711
- */
2712
- select(projection: Record<string, string>): this;
2713
- /**
2714
- * Add a post-fetch filter (supports ${param} syntax)
2715
- * @param field - Field path to filter
2716
- * @param condition - Filter condition
2717
- */
2718
- filter(field: string, condition: FilterValue): this;
2719
- /**
2720
- * Add full-text search
2721
- * @param config - Full-text configuration
2722
- */
2723
- fullText(config: FullTextConfig): this;
2724
- /**
2725
- * Set query parameters
2726
- * @param params - Parameter values
2727
- */
2728
- params(params: Record<string, unknown>): this;
2729
- /**
2730
- * Set a single parameter
2731
- * @param name - Parameter name
2732
- * @param value - Parameter value
2733
- */
2734
- param(name: string, value: unknown): this;
2735
- /**
2736
- * Enable debug mode
2737
- */
2738
- debug(): this;
2739
- /**
2740
- * Skip cache for this request
2741
- */
2742
- skipCache(): this;
2743
- /**
2744
- * Set custom timeout
2745
- * @param ms - Timeout in milliseconds (1000-60000)
2746
- */
2747
- timeout(ms: number): this;
2748
- /**
2749
- * Set cache policy
2750
- * @param ttl - Cache TTL in seconds
2751
- * @param strategy - Cache strategy
2752
- */
2753
- cache(ttl: number, strategy?: 'stale-while-revalidate' | 'cache-first' | 'network-first'): this;
2754
- /**
2755
- * Get the query definition (for debugging)
2756
- */
2757
- getDefinition(): AdhocQueryDefinition;
2758
- /**
2759
- * Get the parameters (for debugging)
2760
- */
2761
- getParameters(): Record<string, unknown>;
2762
- /**
2763
- * Execute the query
2764
- */
2765
- execute(): Promise<AdhocQueryResult<T>>;
2766
- /**
2767
- * Execute and return the records as an array (for list queries).
2768
- */
2769
- items(): Promise<T[]>;
2770
- /**
2771
- * Execute and return the single record (for singleton queries).
2772
- */
2773
- data(): Promise<T | undefined>;
2774
- /**
2775
- * Clone the builder
2776
- */
2777
- clone(): AdhocQueryBuilder<T>;
2778
- }
2779
- /**
2780
- * Client for executing ad-hoc queries
2781
- *
2782
- * @example
2783
- * ```typescript
2784
- * const queryClient = new AdhocQueryClient(httpClient)
2785
- *
2786
- * // Fluent builder
2787
- * const products = await queryClient.build()
2788
- * .from('product')
2789
- * .where({ status: 'active' })
2790
- * .limit(20)
2791
- * .execute()
2792
- *
2793
- * // Direct execution
2794
- * const result = await queryClient.execute({
2795
- * fetch: { contentType: 'product', limit: 10 }
2796
- * })
2797
- * ```
2798
- */
2799
- declare class AdhocQueryClient {
2800
- private readonly httpClient;
2801
- /** The client's locale, asked on every query rather than copied when this client was taken. */
2802
- private readonly clientLocale;
2803
- /**
2804
- * @param locale - The client's locale (`withLocale()`, `updateConfig({ locale })`), sent as the
2805
- * `locale` parameter of every query that binds none: the function `TypedRelyIOClient` hands
2806
- * over, or a locale fixed for this client
2807
- */
2808
- constructor(httpClient: HttpClient, locale?: ClientLocale);
2809
- /**
2810
- * Create a new query builder
2811
- */
2812
- build<T = unknown>(): AdhocQueryBuilder<T>;
2813
- /**
2814
- * Execute a query definition directly
2815
- * @param definition - Query definition
2816
- * @param parameters - Parameter values
2817
- * @param options - Execution options
2818
- * @param request - `headers` this one request adds to the client's own, e.g. `X-RelyIO-Dataset`
2819
- * to query another dataset the key can see. A name the client already sends is replaced for
2820
- * this request only.
2821
- */
2822
- execute<T = unknown>(definition: AdhocQueryDefinition, parameters?: Record<string, unknown>, options?: AdhocQueryOptions, request?: {
2823
- headers?: Record<string, string>;
2824
- }): Promise<AdhocQueryResult<T>>;
2825
- /**
2826
- * Quick fetch helper
2827
- * @param contentType - Content type
2828
- * @param options - Fetch options
2829
- */
2830
- fetch<T = unknown>(contentType: string, options?: Omit<FetchBlock, 'contentType'>): Promise<T[]>;
2831
- /**
2832
- * Quick singleton fetch helper
2833
- * @param singletonKey - Singleton key
2834
- */
2835
- fetchSingleton<T = unknown>(singletonKey: string): Promise<T | undefined>;
2836
- /**
2837
- * Quick aggregate helper
2838
- * @param source - Source content type
2839
- * @param metrics - Metrics to compute
2840
- * @param groupBy - Optional grouping field
2841
- */
2842
- aggregate<T = unknown>(source: string, metrics: Record<string, AggregateMetric>, groupBy?: string | string[]): Promise<T[]>;
2843
- }
2844
-
2845
- /**
2846
- * Content and resource errors.
2847
- *
2848
- * `ContentTypeNotFoundError` and `ContentNotFoundError` were deleted at API5.
2849
- * The mapper reached them by sniffing `CONTENT_TYPE_NOT_FOUND` and
2850
- * `CONTENT_NOT_FOUND`, neither of which is in the closed vocabulary, so neither
2851
- * class was reachable. Both cases are `RESOURCE_NOT_FOUND`, whose declared
2852
- * `details` are `{ kind, id? }` — the same two facts, from the one code a route
2853
- * can actually declare.
2854
- */
2855
- declare class ResourceNotFoundError extends SDKError {
2856
- readonly code = "RESOURCE_NOT_FOUND";
2857
- readonly retryable = false;
2858
- constructor(message?: string, statusCode?: number, cause?: Error, context?: Record<string, unknown>);
2859
- getUserMessage(): string;
2860
- }
2861
- declare class ResourceConflictError extends SDKError {
2862
- readonly code = "RESOURCE_CONFLICT";
2863
- readonly retryable = false;
2864
- constructor(message?: string, statusCode?: number, cause?: Error, context?: Record<string, unknown>);
2865
- getUserMessage(): string;
2866
- }
2867
- declare class ResourceExpiredError extends SDKError {
2868
- readonly code = "RESOURCE_EXPIRED";
2869
- readonly retryable = false;
2870
- constructor(message?: string, statusCode?: number, cause?: Error, context?: Record<string, unknown>);
2871
- getUserMessage(): string;
2872
- }
2873
- /**
2874
- * "Did the service say this resource does not exist?"
2875
- *
2876
- * Reads the **code**, not the status. Four contract codes are 4xx on a read and
2877
- * only one of them means "not there"; the incumbent checked `statusCode === 404`
2878
- * on the thrown value, and a second arm duck-typed a plain object for the same
2879
- * number so a hand-written test double would pass. Both are the branch
2880
- * `INV-API-CLIENT-IS-DERIVED` forbids where a code exists — and the second one
2881
- * only existed because the tests faked the boundary (rule 20). They now stub
2882
- * `fetch`, so the double is gone with the branch.
2883
- */
2884
- declare function isResourceNotFound(error: unknown): boolean;
2885
-
2886
- /**
2887
- * `relyio.changes()` — the changes feed as a typed long-poll iterator
2888
- * (`sdk-surfaces-spec.md` decisions 16–18).
2889
- *
2890
- * Long-poll only, on purpose. `GET /v1/changes` is an ordinary cursor-paged
2891
- * collection the committed document declares, so each poll is one `send` over
2892
- * the one HTTP client (`INV-SS-ONE-CORE`, `INV-API-CLIENT-IS-DERIVED`). The SSE
2893
- * branch the route also serves would be a second transport path inside that
2894
- * client, and `parseResponse` buffers a whole body; it stays for `EventSource`
2895
- * and curl consumers.
2896
- */
2897
-
2898
- type PollChanges = paths['/v1/changes']['get'];
2899
- /**
2900
- * The 200 body the document declares, read from its `application/json` content
2901
- * **by name**. The route also negotiates `text/event-stream`; naming the JSON
2902
- * content is what keeps that alternate from changing this type when the
2903
- * document gains it.
2904
- */
2905
- type ChangesPageBody = PollChanges['responses'][200]['content']['application/json'];
2906
- /**
2907
- * One change, as the committed document describes an item of the feed's `data`
2908
- * (`INV-SS-CHANGE-IS-IDS`: ids, content type, locale, kind and cursor — never a
2909
- * document).
2910
- *
2911
- * Read from the document, never written here: the api's route row names the
2912
- * `Change` component schema, and a change to it reaches this type when the
2913
- * document is regenerated. `changes.types.ts` holds it equal to the document's
2914
- * item, and fails if it is ever `unknown` again.
2915
- */
2916
- type Change = ChangesPageBody['data'][number];
2917
- /**
2918
- * A change as `relyio.changes()` and `relyio.liveChanges()` deliver it: the document's
2919
- * {@link Change}, and `fresh`.
2920
- *
2921
- * `fresh` is the feed position a refetch for this change names, as the one query term the API
2922
- * declares on every read its edge may keep (`?_fresh`, `sdk-surfaces-spec.md` decision 32): hand it
2923
- * to the read — `.fresh(change.fresh)` on `findById()`, `query()` and `search()`, `{ fresh }` to a
2924
- * singleton's `fetch()`. A position no request named before the change is a key no cache holds a
2925
- * body under, so the read is answered after the change — at the edge and in the browser, whether
2926
- * or not the change's purge has reached them — and every later read naming it shares that answer.
2927
- *
2928
- * It is at or after the change, never before: on `/v1/changes` the change's own cursor, the ledger
2929
- * row it came from; at the live edge the cursor of the batch it arrived in. It is not always
2930
- * `cursor`: at the edge an earlier change of a batch carries the position **before** the batch as
2931
- * its `cursor`, so a failure part-way through replays the batch, and a refetch naming that position
2932
- * would be answered from before the change.
2933
- */
2934
- type FeedChange = Change & {
2935
- /** The feed position a refetch for this change names (`?_fresh`); at or after the change. */
2936
- readonly fresh: string;
2937
- };
2938
- /**
2939
- * Why a cursor could no longer be replayed, or why everything must be refetched. `/v1/changes`
2940
- * names `retention` and `dataset-replaced`; the live edge's `feed.resync` may name any reason its
2941
- * wire declares (`VisitorResyncReason` in `@relyio/api-contract`, read from there rather than
2942
- * restated). `unknown` when the message named none.
2943
- */
2944
- type ChangesExpiredReason = VisitorResyncReason | 'unknown';
2945
- /**
2946
- * `RESOURCE_EXPIRED` from the changes feed: the cursor names a position the
2947
- * ledger can no longer replay from (`INV-SS-CURSOR-REPLAYS`). The consumer
2948
- * resyncs from the read path and starts again without a cursor.
2949
- *
2950
- * A `ResourceExpiredError`, so code that already catches that still does. The
2951
- * refusal declares no details, so `reason` is read from the message the api
2952
- * writes (`apps/services/api/src/changes/changes-request.ts` `refusalOf`).
2953
- *
2954
- * The live edge's `feed.resync` — a socket's frame or a poll's answer — is the
2955
- * same instruction and arrives as the same error ({@link ChangesExpiredError.resync}),
2956
- * so `onExpired` means one thing on both transports.
2957
- */
2958
- declare class ChangesExpiredError extends ResourceExpiredError {
2959
- readonly reason: ChangesExpiredReason;
2960
- /**
2961
- * The position the feed resynced to, which a refetch of everything names as `?_fresh`, as a
2962
- * change's {@link FeedChange.fresh} is named: the head a `/v1/changes` resync took, or the cursor
2963
- * the edge's `feed.resync` sent. `undefined` when there was no resync to take one — the error was
2964
- * thrown (`onExpired: 'throw'`) — and on an error a caller built itself.
2965
- */
2966
- readonly fresh: string | undefined;
2967
- /** The base class's signature, so `withContext` can rebuild one; then the position resynced to. */
2968
- constructor(message: string, statusCode?: number, cause?: Error, context?: Record<string, unknown>, fresh?: string);
2969
- /** The base class rebuilds from its four arguments, which would drop the position resynced to. */
2970
- withContext(context: Record<string, unknown>): this;
2971
- /** `/v1/changes`' refusal, with the head the feed resynced to when it did. */
2972
- static of(expired: ResourceExpiredError, fresh?: string): ChangesExpiredError;
2973
- /** The live edge's `feed.resync`: refetch everything, then continue from the cursor it sent. */
2974
- static resync(reason: VisitorResyncReason, cursor: string): ChangesExpiredError;
2975
- }
2976
- interface ChangesOptions {
2977
- /**
2978
- * A `page.nextCursor` — or a change's `cursor` — from an earlier poll. Omitted,
2979
- * the first poll answers the dataset's current position and no changes, at
2980
- * once, and the feed starts from there.
2981
- */
2982
- since?: string;
2983
- /** Content types to keep. Omitted keeps every type; an empty list is refused (400). */
2984
- types?: readonly string[];
2985
- /**
2986
- * Entry ids to keep. Omitted keeps every entry. The api refuses more than it
2987
- * allows (100 today) as `REQUEST_INVALID`, thrown from the iterator as an
2988
- * `InvalidPayloadError` on the first poll; the SDK does not restate the
2989
- * cap, so it cannot drift from the service's.
2990
- */
2991
- ids?: readonly string[];
2992
- /** Ledger rows one poll scans, 1 to 500; the api defaults to 100. */
2993
- limit?: number;
2994
- /**
2995
- * Ends the iteration. An abort — in flight, between polls, or while waiting
2996
- * out a `Retry-After` — makes the `for await` **complete**, with no further
2997
- * request and no `AbortError` thrown into the loop.
2998
- */
2999
- signal?: AbortSignal;
3000
- /**
3001
- * What to do when the cursor has expired.
3002
- *
3003
- * - `'throw'` (default) — the iterator throws {@link ChangesExpiredError}.
3004
- * - `'resync'` — restart from the dataset's current position and keep going.
3005
- * - a function — resync, and call it with the error once the new position is
3006
- * taken and before any change after it is yielded. This is the observable
3007
- * form: whatever the consumer derived from the feed may be stale, so the
3008
- * function is where it invalidates everything — naming the error's `fresh`,
3009
- * the new position, on every refetch. The iterator waits for it.
3010
- */
3011
- onExpired?: 'throw' | 'resync' | ((expired: ChangesExpiredError) => void | Promise<void>);
3012
- /**
3013
- * Called once, when the feed was opened without `since`, with the position it starts from: the
3014
- * first poll's answer, the dataset's head — before the next poll is sent. Read what you show
3015
- * **after** it and a write your read did not see is after the position, so this feed delivers it,
3016
- * with no second request to take the position first (`changesHead()` is that request) (REL-1513).
3017
- *
3018
- * `followChanges()` passes it (`ChangeSourceOptions.reached`), so a feed that drops before its first
3019
- * change reopens from there rather than re-reading everything. Never called with a later empty
3020
- * page's cursor: filtered, that cursor is past rows the filter hid, and a caller that widens the
3021
- * filter must not reopen from it.
3022
- */
3023
- reached?: (cursor: string) => void;
3024
- }
3025
- interface ChangesHeadOptions {
3026
- /** Cancels the request; the promise then rejects. */
3027
- signal?: AbortSignal;
3028
- }
3029
-
3030
- /**
3031
- * `relyio.liveChanges()` — a dataset's published changes at RelyIO's live edge, for a publishable key
3032
- * (`live-layer-spec.md` decision 20; LV5.2, REL-1158).
3033
- *
3034
- * The wire is `@relyio/api-contract`'s `live-visitor.ts`, read from there and never restated: the
3035
- * path and query names, the frames and their reader, the keepalive, the poll's answer and its reader,
3036
- * the poll interval, and what each close code and each refused poll means a client does next.
3037
- *
3038
- * **One open, two ways in.** A subscription opens a WebSocket to the edge. Where it cannot — the
3039
- * runtime has no `WebSocket` (Node 18 and 20), constructing one throws, the socket fails before it
3040
- * opens (a network that blocks sockets), or it has not opened and been caught up within
3041
- * {@link LIVE_SOCKET_OPEN_TIMEOUT_MS} (a network that holds it) — it polls the edge instead, from the
3042
- * same cursor, about every {@link VISITOR_POLL_INTERVAL_MS}, and tries a socket again about once a
3043
- * minute. Both carry one cursor space, so a switch either way loses nothing.
3044
- *
3045
- * **Where it starts.** Without `since`, the edge answers the dataset's current position before
3046
- * anything else — the socket's `feed.opened`, or the first poll's answer — and the subscription hands
3047
- * it to `reached`. That call is the position, with no request of its own for it: a read sent after it
3048
- * is answered at or after the position, and every change after it is delivered here, so a publish the
3049
- * read did not see is announced (REL-1513). The edge counts the subscription as one live connection,
3050
- * whichever way in it took (`INV-LV-LIVE-IS-METERED`).
3051
- *
3052
- * **It ends only when its signal aborts, and throws every other end** — a socket closed by a refusal
3053
- * or a deploy, a refused poll, a keepalive nobody answered — as a `LiveEdgeError` saying what to do
3054
- * next, so `followChanges()` reconnects, backs off or stops by the edge's own tables. That loop is
3055
- * the one reconnect policy (`INV-SS-ONE-CORE`, REL-1215); this module keeps no back-off of its own.
3056
- *
3057
- * **Cursors.** The edge sends one cursor after a batch of changes. The last change of a batch carries
3058
- * it; each earlier one carries the position before the batch, so a failure part-way through a batch
3059
- * replays the whole batch rather than skipping its rest. Delivery is at-least-once: a replay and the
3060
- * live frames after it may name one change twice, and a refetch makes a repeat cost a read, never a
3061
- * wrong view (`INV-SS-CHANGE-IS-IDS`).
3062
- *
3063
- * **What a refetch names.** Every change of a batch carries the batch's own cursor as `fresh`
3064
- * (`FeedChange`), the position its refetch names as `?_fresh` — not `cursor`, which for an earlier
3065
- * change is the position before the batch: the previous batch's refetches were answered under that
3066
- * one, before this batch, and a cache would hand the same answer back. A `feed.resync`'s error
3067
- * carries the cursor it sent the same way.
3068
- */
3069
-
3070
- /**
3071
- * How often an open socket sends the keepalive, which the edge's runtime answers without waking the
3072
- * feed — the Studio's dataset room sends its own on the same interval. A socket that has heard
3073
- * nothing by the next one, not even the answer, is presumed dead and reconnected: a client that only
3074
- * receives never learns otherwise that a network change dropped its connection.
3075
- */
3076
- declare const LIVE_KEEPALIVE_INTERVAL_MS = 30000;
3077
- /**
3078
- * How long a socket has to open and be caught up — the feed's `feed.opened` — before the subscription
3079
- * stops waiting on it, closes it and polls instead, as it does for a socket that fails before it opens
3080
- * (REL-1513).
3081
- *
3082
- * The poll interval, because that is what a socket is for: telling the page sooner than a poll would.
3083
- * A poll is answered at once and polled again within this interval, so a socket that has not caught
3084
- * up by then has already served the page worse than polling would have. It is well past a healthy
3085
- * open — the door's checks and one ledger read at the edge — and half the keepalive's verdict on an
3086
- * open socket that has gone quiet ({@link LIVE_KEEPALIVE_INTERVAL_MS}, twice). It bounds how long
3087
- * `<RelyIOProvider live>`'s first reads wait for a position on a network that neither opens a socket
3088
- * nor refuses one — a proxy that holds the upgrade — which without it they would wait on for as long
3089
- * as the page is open.
3090
- */
3091
- declare const LIVE_SOCKET_OPEN_TIMEOUT_MS = 15000;
3092
- interface LiveChangesOptions {
3093
- /**
3094
- * A change's cursor from an earlier subscription. Omitted, the edge answers the dataset's current
3095
- * position first and replays nothing, and the subscription hands that position to `reached` before
3096
- * anything after it. Read what you show **after** that first call — a read sent before it, from a
3097
- * subscription opened afterwards without `since`, leaves a publish between the two that neither
3098
- * sees (REL-1513). With `followChanges()`, which hands the source its own `reached`, call both —
3099
- * and let the read go if the loop fails first, so content never waits on a feed that is down:
3100
- *
3101
- * ```ts
3102
- * let positioned!: () => void
3103
- * const position = new Promise<void>(resolve => (positioned = resolve))
3104
- * const following = followChanges(
3105
- * from =>
3106
- * relyio.liveChanges({
3107
- * ...from,
3108
- * ids: [id],
3109
- * reached: cursor => {
3110
- * from.reached(cursor)
3111
- * positioned()
3112
- * },
3113
- * }),
3114
- * { signal, onChange, onResync, onState: state => state.kind !== 'following' && positioned() },
3115
- * )
3116
- * await position
3117
- * const post = await relyio.content('post').findById(id).execute()
3118
- * ```
3119
- *
3120
- * The wait is bounded: a socket that has not opened and been told its position within
3121
- * {@link LIVE_SOCKET_OPEN_TIMEOUT_MS} is given up for a poll, answered at once.
3122
- */
3123
- since?: string;
3124
- /** Content types to keep. Omitted keeps every type; an empty list is refused. At most 50. */
3125
- types?: readonly string[];
3126
- /** Entry ids to keep. Omitted keeps every entry; an empty list is refused. At most 100. */
3127
- ids?: readonly string[];
3128
- /**
3129
- * Ends the subscription. An abort — while connected, while polling, or between polls — makes the
3130
- * `for await` **complete**, with the socket closed, no further request and nothing thrown.
3131
- */
3132
- signal?: AbortSignal;
3133
- /**
3134
- * What to do when the edge says to refetch everything (`feed.resync`: the cursor is too old, was
3135
- * never this dataset's, or the dataset was replaced) — as `relyio.changes()` takes it: `'throw'`
3136
- * (default) throws a `ChangesExpiredError`, `'resync'` carries on from the edge's position, and a
3137
- * function is awaited with the error before any change after that position is yielded.
3138
- */
3139
- onExpired?: ChangesOptions['onExpired'];
3140
- /**
3141
- * Called when the edge has the subscription in hand — its `feed.opened` frame, or an answered
3142
- * poll. `followChanges()` passes it to reset its back-off; see `ChangeSourceOptions.opened`.
3143
- */
3144
- opened?: () => void;
3145
- /**
3146
- * Called with a position reached without a change to carry it. `followChanges()` passes it so a
3147
- * connection dropped before its first change reconnects from there; see
3148
- * `ChangeSourceOptions.reached`. Without `since`, its first call is the position the subscription
3149
- * starts from (see `since`).
3150
- */
3151
- reached?: (cursor: string) => void;
3152
- }
3153
-
3154
- /**
3155
- * The key's project, as `GET /v1/project` describes it: organization, project, dataset and branch,
3156
- * each by id and name, and `studioUrl` — the Studio root the preview toolbar deep-links from
3157
- * (`sdk-surfaces-spec.md` decision 24), or `null` when the service has no Dashboard address.
3158
- *
3159
- * Read from the committed document's `application/json` content by name, never written here
3160
- * (`INV-API-CLIENT-IS-DERIVED`); `derived-transport.types.ts` holds the two equal.
3161
- */
3162
- type Project = paths['/v1/project']['get']['responses'][200]['content']['application/json']['data'];
3163
- /**
3164
- * Client configuration with optional schema
3165
- */
3166
- interface TypedClientConfig<TSchema extends AnySchemaDefinition | undefined = undefined> extends RelyIOConfig {
3167
- /** Schema definition for type inference (optional - omit for dynamic/untyped usage) */
3168
- schema?: TSchema;
3169
- }
3170
- /**
3171
- * Typed RelyIO client with full schema awareness
3172
- *
3173
- * @example
3174
- * ```typescript
3175
- * import { createClient } from '@relyio/sdk'
3176
- * import { schema } from './schema'
3177
- *
3178
- * const relyio = createClient({
3179
- * apiKey: process.env.RELYIO_API_KEY!,
3180
- * schema,
3181
- * })
3182
- *
3183
- * // Content operations
3184
- * const products = await relyio.content('product').query().execute()
3185
- *
3186
- * // Search
3187
- * const results = await relyio.search('wireless').in('product').execute()
3188
- *
3189
- * // Batch operations
3190
- * const batch = await relyio.batch()
3191
- * .create('product', { content: { title: 'New' } })
3192
- * .execute()
3193
- * ```
3194
- */
3195
- declare class TypedRelyIOClient<TSchema extends AnySchemaDefinition | undefined = undefined> {
3196
- /**
3197
- * The one HTTP client every sub-client sends through. `updateConfig()` and `setApiKey()` change it
3198
- * in place rather than replacing it, so a sub-client taken before either sends through the change.
3199
- */
3200
- private readonly httpClient;
3201
- private config;
3202
- /**
3203
- * The configuration as `createClient()`, `setApiKey()` and `updateConfig()` gave it, before the
3204
- * `relyio dev` handshake: what the next update is laid over, and what `withLocale()` starts from.
3205
- */
3206
- private configured;
3207
- private _sandbox;
3208
- private _schema;
3209
- /**
3210
- * The client's locale as it is when a call is made. Every sub-client and builder is handed this
3211
- * rather than a copy of the locale, so one taken before `updateConfig({ locale })` sends the new
3212
- * locale, or none after `locale: null` (REL-1453).
3213
- */
3214
- private readonly currentLocale;
3215
- /** The live edge's poll client id: chosen once, when first needed, and kept for this client's life. */
3216
- private _liveClientId;
3217
- constructor(config: TypedClientConfig<TSchema>);
3218
- /**
3219
- * Get the schema definition
3220
- */
3221
- get schema(): TSchema;
3222
- /**
3223
- * The `relyio dev` sandbox this client resolved at construction, as the preview toolbar displays
3224
- * it (`sdk-surfaces-spec.md` decision 22).
3225
- *
3226
- * Plain, JSON-serializable data and no key, so a server component can hand it to a client
3227
- * component as a prop — the browser cannot read the handshake itself, so a client created there
3228
- * reports `absent`. See {@link SandboxState} for what each state means.
3229
- *
3230
- * @example
3231
- * ```tsx
3232
- * // app/layout.tsx — a server component handing the state to a client component
3233
- * <SandboxBadge sandbox={relyio.sandbox} />
3234
- * ```
3235
- */
3236
- get sandbox(): SandboxState;
3237
- /**
3238
- * Get the ad-hoc query client. Every query it sends is in the client's locale as it is then.
3239
- */
3240
- get query(): AdhocQueryClient;
3241
- /**
3242
- * Get a content client for a specific content type
3243
- * Type-safe when schema is provided: autocompletes content type names AND field names
3244
- *
3245
- * @example
3246
- * ```typescript
3247
- * const products = await relyio.content('product')
3248
- * .query()
3249
- * .where({ status: 'active' }) // ← Field names autocomplete with schema!
3250
- * .locale('en-US')
3251
- * .limit(20)
3252
- * .execute()
3253
- * ```
3254
- */
3255
- content<K extends TSchema extends AnySchemaDefinition ? SchemaContentTypeNames<TSchema> : string>(contentType: K): TSchema extends AnySchemaDefinition ? ContentClient<InferContentType<TSchema['contentTypes'][K & keyof TSchema['contentTypes']], TSchema>, ContentReading<TSchema['contentTypes'][K & keyof TSchema['contentTypes']], TSchema>> : ContentClient<BaseContent>;
3256
- /**
3257
- * Get an asset client for a specific asset type.
3258
- *
3259
- * Mirrors `content()` but targets `entryType: 'asset'` entries on the same
3260
- * dynamic `/{assetTypeId}` routes, and adds the `file(entryUid)` sub-resource
3261
- * builder + a high-level `upload()` helper. Type-safe when a schema is
3262
- * provided: autocompletes asset type names AND metadata field names.
3263
- *
3264
- * @example
3265
- * ```typescript
3266
- * const photo = await relyio.asset('productPhoto').upload(file)
3267
- * .filename('shoe.jpg')
3268
- * .visibility('public')
3269
- * .execute()
3270
- *
3271
- * const heroUrl = await relyio.asset('productPhoto').file(photo._metadata.id).url({ variantId: 'hero' })
3272
- * ```
3273
- */
3274
- asset<K extends TSchema extends AnySchemaDefinition ? SchemaAssetTypeNames<TSchema> : string>(assetType: K): TSchema extends AnySchemaDefinition ? AssetClient<InferAssetType<NonNullable<TSchema['assetTypes']>[K & keyof NonNullable<TSchema['assetTypes']>], TSchema>, AssetReading<NonNullable<TSchema['assetTypes']>[K & keyof NonNullable<TSchema['assetTypes']>], TSchema>> : AssetClient<BaseContent>;
3275
- /**
3276
- * Get a singleton client for a specific singleton key
3277
- * Type-safe when schema is provided: autocompletes singleton names AND field names
3278
- *
3279
- * @example
3280
- * ```typescript
3281
- * const homepage = await relyio.singleton('homepage')
3282
- * .include(['featuredProducts'])
3283
- * .locale('en')
3284
- * .fetch()
3285
- * ```
3286
- */
3287
- singleton<K extends TSchema extends AnySchemaDefinition ? SchemaSingletonNames<TSchema> : string>(key: K): TSchema extends AnySchemaDefinition ? SingletonClient<InferSingleton<TSchema['singletonContentTypes'][K & keyof TSchema['singletonContentTypes']], TSchema>, SingletonReading<TSchema['singletonContentTypes'][K & keyof TSchema['singletonContentTypes']], TSchema>> : SingletonClient<unknown>;
3288
- /**
3289
- * Get a settings client for a specific settings group
3290
- * Type-safe when schema is provided: autocompletes settings group names AND field names
3291
- *
3292
- * @example
3293
- * ```typescript
3294
- * const emailConfig = await relyio.settings('email-config')
3295
- * .include(['defaultTemplate'])
3296
- * .fetch()
3297
- * ```
3298
- */
3299
- settings<K extends TSchema extends AnySchemaDefinition ? SchemaSettingsGroupNames<TSchema> : string>(groupId: K): TSchema extends AnySchemaDefinition ? SettingsClient<InferSettingsGroup<NonNullable<TSchema['settingsGroups']>[K & keyof NonNullable<TSchema['settingsGroups']>], TSchema>, SettingsReading<NonNullable<TSchema['settingsGroups']>[K & keyof NonNullable<TSchema['settingsGroups']>], TSchema>> : SettingsClient<unknown>;
3300
- /**
3301
- * Start a search query with full-text, semantic, or hybrid search.
3302
- *
3303
- * Where a schema types the client, the results are its entries — every content type's until
3304
- * `in()` or `across()` narrows them (`INV-SS-TYPES-INFERRED`).
3305
- *
3306
- * @example
3307
- * ```typescript
3308
- * const results = await relyio.search('wireless headphones')
3309
- * .in('product')
3310
- * .where({ status: 'active' })
3311
- * .limit(20)
3312
- * .includeScore()
3313
- * .execute()
3314
- * ```
3315
- */
3316
- search<T extends BaseContent = SearchEntry<TSchema>>(query: string): SearchBuilder<T, TSchema>;
3317
- /**
3318
- * Start an autocomplete query for search suggestions
3319
- *
3320
- * @example
3321
- * ```typescript
3322
- * const suggestions = await relyio.autocomplete('wire')
3323
- * .in('product')
3324
- * .field('title')
3325
- * .fuzzy()
3326
- * .limit(10)
3327
- * .execute()
3328
- * ```
3329
- */
3330
- autocomplete(query: string): AutocompleteBuilder;
3331
- /**
3332
- * Find content similar to a given entry using vector search
3333
- *
3334
- * @example
3335
- * ```typescript
3336
- * const similar = await relyio.similar('entry_abc123')
3337
- * .in('product')
3338
- * .locale('fr-FR')
3339
- * .limit(5)
3340
- * .execute()
3341
- * ```
3342
- */
3343
- similar<T extends BaseContent = BaseContent>(sourceId: string): SimilarBuilder<T>;
3344
- /**
3345
- * Start a batch operation for bulk create/update/delete.
3346
- *
3347
- * By default each operation lands or fails on its own. `atomic: true` makes the batch one
3348
- * transaction, which the API accepts only when every operation is the same kind of write and none
3349
- * writes a singleton that does not exist yet.
3350
- *
3351
- * @example
3352
- * ```typescript
3353
- * const result = await relyio.batch()
3354
- * .create('product', { content: { title: 'iPhone', price: 999 } })
3355
- * .update('product', 'entry_abc', { content: { price: 899 } })
3356
- * .delete('product', 'entry_xyz')
3357
- * .upsertSingleton('homepage', { content: { hero: 'Welcome!' } })
3358
- * .execute()
3359
- *
3360
- * console.log(result.summary.succeeded)
3361
- *
3362
- * // All or nothing: one kind of write
3363
- * await relyio.batch()
3364
- * .update('product', 'entry_abc', { content: { price: 899 } })
3365
- * .update('product', 'entry_def', { content: { price: 799 } })
3366
- * .execute({ atomic: true })
3367
- * ```
3368
- */
3369
- batch(): BatchBuilder<NonNullable<TSchema>>;
3370
- /** The search client, whose builders send the client's locale as it is when each one is sent. */
3371
- private searchClient;
3372
- /**
3373
- * Follow the dataset's changes feed: each write this key can see, as ids, in
3374
- * order, long-polled from `GET /v1/changes`.
3375
- *
3376
- * Lazy — nothing is requested until the first `next()`. It runs until
3377
- * `signal` aborts, which ends the `for await` cleanly rather than throwing.
3378
- * A change names what changed; refetch it through the ordinary read path
3379
- * (`INV-SS-CHANGE-IS-IDS`), naming its `fresh` so no cache answers with the
3380
- * body from before it (`FeedChange`).
3381
- *
3382
- * @example
3383
- * ```typescript
3384
- * const controller = new AbortController()
3385
- * for await (const change of relyio.changes({ since: cursor, types: ['post'], signal: controller.signal })) {
3386
- * // { entryId, contentType, locale, kind, cursor, fresh }
3387
- * await relyio.content(change.contentType).findById(change.entryId).fresh(change.fresh).execute()
3388
- * }
3389
- * ```
3390
- *
3391
- * @example Resync when the cursor has expired
3392
- * ```typescript
3393
- * for await (const change of relyio.changes({
3394
- * since: cursor,
3395
- * onExpired: async expired => refetchEverything(expired.fresh),
3396
- * })) { … }
3397
- * ```
3398
- */
3399
- changes(options?: ChangesOptions): AsyncIterable<FeedChange>;
3400
- /**
3401
- * Follow the dataset's **published** changes at RelyIO's live edge, with a publishable key
3402
- * (`rly_pub_…`): each change as ids, in order — the transport a browser follows changes with.
3403
- *
3404
- * A WebSocket to the edge, from `since` or the dataset's current position; where a socket cannot
3405
- * open — the runtime has none, the network refuses it, or it has not opened and been told its
3406
- * position within `LIVE_SOCKET_OPEN_TIMEOUT_MS` — it polls the edge instead, about every fifteen
3407
- * seconds, from the same cursor. The edge is `liveUrl`, or derived from the default `baseUrl`
3408
- * (see `RelyIOConfig.liveUrl`).
3409
- *
3410
- * Without `since` it starts at the dataset's current position, which it hands `reached` before
3411
- * anything after it: read what you show after that call, and a publish your read did not see is
3412
- * one this subscription announces (see `LiveChangesOptions.since`).
3413
- *
3414
- * One subscription ends only when `signal` aborts, which ends the `for await` cleanly; every other
3415
- * end — a socket a deploy dropped, a refusal, a refused poll — is thrown as a `LiveEdgeError`
3416
- * that says whether to stop, retry or back off. Hand it to `followChanges()`, the one loop that
3417
- * reconnects from the last cursor with jittered back-off and honours those answers:
3418
- *
3419
- * @example
3420
- * ```typescript
3421
- * const relyio = createClient({ apiKey: 'rly_pub_…' })
3422
- * const controller = new AbortController()
3423
- * await followChanges(from => relyio.liveChanges({ ...from, types: ['post'] }), {
3424
- * signal: controller.signal,
3425
- * // `fresh` names a position no cache holds a body for from before the change.
3426
- * onChange: change =>
3427
- * relyio.content(change.contentType).findById(change.entryId).fresh(change.fresh).execute(),
3428
- * onResync: resync => refetchEverything(resync.cause === 'expired' ? resync.expired.fresh : undefined),
3429
- * })
3430
- * ```
3431
- *
3432
- * A client without a publishable key sends nothing: a secret or preview key is never put in the
3433
- * edge's URL. On a server, follow `changes()` with it instead.
3434
- */
3435
- liveChanges(options?: LiveChangesOptions): AsyncIterable<FeedChange>;
3436
- /**
3437
- * The dataset's current position in the changes feed, as a cursor to start
3438
- * `changes({ since })` or `followChanges(…, { since })` from. One
3439
- * `GET /v1/changes` without a cursor: answered at once, holding nothing.
3440
- *
3441
- * Take it **before** you read what you will follow. A write your read did not
3442
- * see is then after the position and replayed; opening the feed afterwards
3443
- * without a `since` starts after it and misses it.
3444
- *
3445
- * @example
3446
- * ```typescript
3447
- * const since = await relyio.changesHead()
3448
- * const post = await relyio.content('post').findById(id).execute()
3449
- * for await (const change of relyio.changes({ since, ids: [id] })) { … }
3450
- * ```
3451
- */
3452
- changesHead(options?: ChangesHeadOptions): Promise<string>;
3453
- /**
3454
- * Build and execute an ad-hoc query dynamically
3455
- *
3456
- * @example
3457
- * ```typescript
3458
- * const result = await relyio.adhoc()
3459
- * .from('product')
3460
- * .where({ status: 'active' })
3461
- * .include(['brand', 'categories'])
3462
- * .select({
3463
- * id: 'id',
3464
- * title: 'title',
3465
- * price: 'pricing.basePrice'
3466
- * })
3467
- * .limit(20)
3468
- * .execute()
3469
- * ```
3470
- */
3471
- adhoc<T = unknown>(): AdhocQueryBuilder<T>;
3472
- /**
3473
- * Execute a predefined query by name (from schema)
3474
- * Type-safe when schema is provided: autocompletes query names
3475
- *
3476
- * @example
3477
- * ```typescript
3478
- * const featured = await relyio.executeQuery('featured-products', {
3479
- * limit: 10,
3480
- * category: 'electronics'
3481
- * })
3482
- * ```
3483
- */
3484
- executeQuery<K extends TSchema extends AnySchemaDefinition ? SchemaQueryNames<TSchema> : string>(queryName: K, params?: Record<string, unknown>): Promise<unknown[]>;
3485
- /**
3486
- * Describe the project this key resolves to — `GET /v1/project`.
3487
- *
3488
- * `dataset` is the one this client's requests select (`dataset`, a sandbox from the `relyio dev`
3489
- * handshake, or the key's default), which is not always the project's default, so a Studio link
3490
- * built from `studioUrl` should carry it as `?dataset=`. `studioUrl` is `null` — a value, not an
3491
- * error — when the service has no Dashboard address configured.
3492
- *
3493
- * @example
3494
- * ```typescript
3495
- * const { studioUrl, dataset } = await relyio.project()
3496
- * if (studioUrl !== null) window.open(`${studioUrl}?dataset=${dataset.id}`)
3497
- * ```
3498
- */
3499
- project(): Promise<Project>;
3500
- /**
3501
- * Get schema information from the API
3502
- */
3503
- getSchemaInfo(contentType?: string): Promise<unknown>;
3504
- /**
3505
- * List all content types from the API
3506
- */
3507
- listContentTypes(): Promise<Array<{
3508
- id: string;
3509
- name: string;
3510
- type: 'content' | 'singleton';
3511
- }>>;
3512
- /**
3513
- * Get API health status
3514
- */
3515
- health(): Promise<{
3516
- status: 'ok' | 'degraded' | 'down';
3517
- timestamp: string;
3518
- }>;
3519
- /**
3520
- * Check if the API is ready
3521
- */
3522
- ready(): Promise<boolean>;
3523
- /**
3524
- * Test the connection to the API
3525
- */
3526
- testConnection(): Promise<boolean>;
3527
- /**
3528
- * Change some of the client's options and keep the rest.
3529
- *
3530
- * What you pass is laid over the configuration the client holds, field by field:
3531
- *
3532
- * - an option you name replaces the value the client holds;
3533
- * - an option left out, or given as `undefined`, keeps the value the client holds;
3534
- * - an option with no default given as `null` is cleared — `locale`, `dataset`, `apiKey`, `dev`,
3535
- * `fetch`, `browser.onCorsError`, `realtime.wsUrl`, `debug.logger` — so the client runs as though
3536
- * it had never been given one. An option with a default takes no `null`;
3537
- * - a bag of options you name — `browser`, `cache`, `realtime`, `debug` — keeps every field of it
3538
- * you do not name, so `updateConfig({ browser: { includeCredentials: true } })` keeps a
3539
- * configured `onCorsError`;
3540
- * - `headers` is laid over the headers the client sends, a name matched without regard to case: a
3541
- * header you name is added or replaces every spelling of it, every other one is still sent, and a
3542
- * header given as `null` is no longer sent;
3543
- * - naming `apiKey` — a key, or `null` — replaces every key the client holds, one given in
3544
- * `headers` included.
3545
- *
3546
- * The configuration it is laid over is what `createClient()`, `setApiKey()` and earlier updates
3547
- * gave the client. The `relyio dev` handshake is read again, as `createClient()` reads it: while a
3548
- * sandbox is active its key and dataset are sent over the configured ones, and once it has gone
3549
- * the configured ones are.
3550
- *
3551
- * The client's one HTTP client is changed in place, so every request from now on is sent as the
3552
- * update says — including one from a `content()`, `asset()`, `singleton()` or `settings()`
3553
- * sub-client, a `query` client, or a builder such as `search()` or `batch()`, taken before the
3554
- * call. The locale reaches them too: each reads the client's locale when it sends, so a new
3555
- * `locale` is the default of its next request and `locale: null` leaves that request with none. A
3556
- * locale the call names, and a sub-client's own `withLocale()`, still win over it. A client made
3557
- * by `withLocale()` is a client of its own: an update of this one does not reach it.
3558
- *
3559
- * @example
3560
- * ```typescript
3561
- * const relyio = createClient({ apiKey, headers: { 'X-Tenant': 'acme' }, browser: { onCorsError } })
3562
- *
3563
- * relyio.updateConfig({ headers: { 'X-Trace': traceId } }) // X-Tenant is still sent
3564
- * relyio.updateConfig({ browser: { includeCredentials: true } }) // onCorsError is kept
3565
- * relyio.updateConfig({ headers: { 'X-Tenant': null } }) // X-Tenant is no longer sent
3566
- * relyio.updateConfig({ locale: null }) // reads name no locale of the client's
3567
- * ```
3568
- */
3569
- updateConfig(update: RelyIOConfigUpdate): void;
3570
- /**
3571
- * Get the current configuration
3572
- */
3573
- getConfig(): Readonly<MergedRelyIOConfig>;
3574
- /**
3575
- * Replace the API key every later request sends as `X-API-Key`.
3576
- *
3577
- * It reaches every request this client makes from now on, including those of a `content()`,
3578
- * `asset()` or other sub-client taken before the call, and `getConfig().apiKey` reports it. A key
3579
- * given in `headers`, under any spelling, is replaced with it and does not come back on a later
3580
- * `updateConfig()`. A client already derived with `withLocale()` is a client of its own and keeps
3581
- * its key.
3582
- *
3583
- * An empty or blank key is no key, as it is when passed to `createClient`: the next request
3584
- * rejects with `MissingApiKeyError` and nothing is sent.
3585
- *
3586
- * Under an active `relyio dev` sandbox the sandbox's scoped key stands, as it does over the key
3587
- * passed to `createClient` — replacing the configured key does not retarget the client at the
3588
- * sandbox's dataset with a key the sandbox did not issue.
3589
- *
3590
- * @example
3591
- * ```typescript
3592
- * const relyio = createClient({ apiKey: process.env.RELYIO_API_KEY! })
3593
- * relyio.setApiKey(rotatedKey) // the next read sends `X-API-Key: <rotatedKey>`
3594
- * ```
3595
- */
3596
- setApiKey(apiKey: string): void;
3597
- /**
3598
- * Get the HTTP client instance (for advanced usage)
3599
- */
3600
- getHttpClient(): HttpClient;
3601
- /**
3602
- * Create a locale-scoped client
3603
- *
3604
- * Every call the returned client builds is in this locale unless the call names one of its own,
3605
- * which wins: every read — `content()`, `asset()`, `singleton()`, `settings()`, `search()`,
3606
- * `similar()`, `autocomplete()`, `executeQuery()` and the ad-hoc queries — and every write and
3607
- * version-history read — `create()` and `update()`, the singleton and settings writes, the asset
3608
- * file writes, `versions()` and `version()`, and each operation `batch()` builds. `restore()`
3609
- * always names its locale; a delete, and a `batch().raw()` operation, carry none of the client's.
3610
- *
3611
- * Type-safe when schema is provided: autocompletes valid locale codes!
3612
- *
3613
- * @example
3614
- * ```typescript
3615
- * const spanish = relyio.withLocale('es-ES') // ← Autocompletes with schema!
3616
- *
3617
- * // Reads and writes use Spanish
3618
- * const products = await spanish.content('product').query().execute()
3619
- * const homepage = await spanish.singleton('homepage').fetch()
3620
- * const results = await spanish.search('zapatos').execute()
3621
- * await spanish.content('product').update(id, { title: 'Zapatos' })
3622
- *
3623
- * // Original client unchanged
3624
- * const englishProducts = await relyio.content('product').query().execute()
3625
- * ```
3626
- */
3627
- withLocale<L extends TSchema extends AnySchemaDefinition ? SchemaLocaleCodes<TSchema> : string>(locale: L): TypedRelyIOClient<TSchema>;
3628
- /**
3629
- * Get the current default locale
3630
- */
3631
- getLocale(): string | undefined;
3632
- /**
3633
- * Get available locales from the API
3634
- *
3635
- * @example
3636
- * ```typescript
3637
- * const { locales, defaultLocale } = await relyio.getLocales()
3638
- *
3639
- * // Build language switcher
3640
- * const options = locales.map(l => ({
3641
- * code: l.code,
3642
- * label: l.displayName,
3643
- * isDefault: l.default,
3644
- * }))
3645
- * ```
3646
- */
3647
- getLocales(search?: string): Promise<{
3648
- locales: Array<{
3649
- code: string;
3650
- displayName?: string;
3651
- default?: boolean;
3652
- fallback?: string[];
3653
- direction?: 'ltr' | 'rtl';
3654
- active?: boolean;
3655
- }>;
3656
- defaultLocale: string | null;
3657
- total: number;
3658
- }>;
3659
- /**
3660
- * Get the default locale from the API
3661
- */
3662
- getDefaultLocale(): Promise<string | null>;
3663
- }
3664
- /**
3665
- * Create a RelyIO client
3666
- *
3667
- * @example Without schema (dynamic usage)
3668
- * ```typescript
3669
- * import { createClient } from '@relyio/sdk'
3670
- *
3671
- * const relyio = createClient({
3672
- * apiKey: process.env.RELYIO_API_KEY!,
3673
- * })
3674
- *
3675
- * // Works but no autocomplete for content types
3676
- * const products = await relyio.content('product').fetchAll()
3677
- * ```
3678
- *
3679
- * @example With schema (fully typed)
3680
- * ```typescript
3681
- * import { createClient } from '@relyio/sdk'
3682
- * import { schema } from './schema'
3683
- *
3684
- * const relyio = createClient({
3685
- * apiKey: process.env.RELYIO_API_KEY!,
3686
- * schema,
3687
- * })
3688
- *
3689
- * // Content type names autocomplete!
3690
- * const products = await relyio.content('product').fetchAll()
3691
- * ```
3692
- *
3693
- * @example After `relyio generate-types` (inferred, `INV-SS-TYPES-INFERRED`)
3694
- * ```typescript
3695
- * import { createClient } from '@relyio/sdk'
3696
- *
3697
- * // The generated `schema.ts` augments `Register`, so no generic and no `schema:` are needed
3698
- * const relyio = createClient({ apiKey: process.env.RELYIO_API_KEY! })
3699
- * ```
3700
- *
3701
- * The overload without a schema comes first on purpose. Behind the generic one it was never
3702
- * chosen: with no `schema` to infer from, `TSchema` fell back to its constraint and the call
3703
- * resolved to `TypedRelyIOClient<AnySchemaDefinition>`. First, it takes every call that passes no
3704
- * schema, and returns the registered schema's client — or the untyped one when nothing registered.
3705
- * A `schema:` value fails its `schema?: undefined`, and an explicit generic skips it because it has
3706
- * no type parameter, so both reach the generic overload unchanged.
3707
- */
3708
- declare function createClient(config: RelyIOConfig & {
3709
- schema?: undefined;
3710
- }): TypedRelyIOClient<RegisteredSchema>;
3711
- declare function createClient<TSchema extends AnySchemaDefinition>(config: TypedClientConfig<TSchema>): TypedRelyIOClient<TSchema>;
3712
- /**
3713
- * Helper type to extract schema from client
3714
- */
3715
- type ClientSchema<T extends TypedRelyIOClient<AnySchemaDefinition>> = T extends TypedRelyIOClient<infer TSchema> ? TSchema : never;
3716
-
3717
- export { type BatchOptions as $, type ActiveSandbox as A, type SearchMode as B, type Change as C, type SearchResultItem as D, type ExtendedQueryResult as E, type FeedChange as F, type SearchHitMetadata as G, HttpClient as H, type SearchResponse as I, type AutocompleteSuggestion as J, type AutocompleteResponse as K, LIVE_KEEPALIVE_INTERVAL_MS as L, type SimilarResult as M, type SimilarResponse as N, type SearchFilter as O, type Project as P, type SearchFields as Q, type Register as R, type SandboxState as S, TypedRelyIOClient as T, type SearchSort as U, type SearchTypeName as V, type SearchEntry as W, BatchClient as X, BatchBuilder as Y, type BatchMethod as Z, type BatchOperation as _, ChangesExpiredError as a, type BatchOperationResult as a0, type BatchSummary as a1, type BatchResponse as a2, AdhocQueryClient as a3, AdhocQueryBuilder as a4, type FetchBlock as a5, type AggregateBlock as a6, type AggregateMetric as a7, type SearchBlock as a8, type FilterValue as a9, type MultipartTarget as aA, type UploadProgress as aB, ResourceNotFoundError as aC, ResourceConflictError as aD, ResourceExpiredError as aE, isResourceNotFound as aF, type FileErrorCode as aG, type FileUploadErrorCode as aH, FileUploadError as aI, FileValidationError as aJ, type FileValidationConstraints as aK, validateFile as aL, type FullTextConfig as aa, type AdhocQueryDefinition as ab, type AdhocQueryOptions as ac, type QueryMetadata as ad, type AdhocQueryResult as ae, AssetClient as af, AssetFileBuilder as ag, AssetUploadBuilder as ah, isAssetEntry as ai, type AssetVisibility as aj, type FileStatus as ak, type FileBackend as al, type AssetFileExtracted as am, type FileStatusResult as an, type AssetEntryResponse as ao, type PopulatedAssetReference as ap, type PresignInput as aq, type PresignResult as ar, type ConfirmInput as as, type ConfirmResult as at, type ResolveUrlInput as au, type ResolveUrlResult as av, type CreateAssetOptions as aw, type WaitUntilReadyOptions as ax, type MultipartCreateResult as ay, type MultipartPart as az, type ChangesExpiredReason as b, createClient as c, type TypedClientConfig as d, type ClientSchema as e, type RegisteredSchema as f, type SendOptions as g, entityTagOf as h, type ConditionalWriteOptions as i, type RetryableWriteOptions as j, type ChangesHeadOptions as k, type ChangesOptions as l, LIVE_SOCKET_OPEN_TIMEOUT_MS as m, type LiveChangesOptions as n, ContentClient as o, SingletonClient as p, SettingsClient as q, ContentQueryBuilder as r, ContentByIdBuilder as s, type ChildrenOptions as t, type SingletonFetchOptions as u, type SettingsFetchOptions as v, SearchClient as w, SearchBuilder as x, AutocompleteBuilder as y, SimilarBuilder as z };