@relyio/sdk 0.13.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +144 -0
- package/dist/{chunk-3NPPMGB6.js → chunk-5UMY6CS4.js} +2 -2
- package/dist/{chunk-6NGJXLOR.js → chunk-DBBZVZOS.js} +968 -2681
- package/dist/{chunk-DEDIJKHY.cjs → chunk-GUPV2M3X.cjs} +969 -2691
- package/dist/{chunk-EW23SZPX.js → chunk-PCFTJLEP.js} +540 -262
- package/dist/{chunk-Z6GLWSSD.cjs → chunk-RSHMA4KT.cjs} +2 -2
- package/dist/{chunk-FOSM4LMM.cjs → chunk-X4KSS7PP.cjs} +539 -262
- package/dist/client/index.cjs +3 -3
- package/dist/client/index.d.cts +3 -3
- package/dist/client/index.d.ts +3 -3
- package/dist/client/index.js +1 -1
- package/dist/{include-paths-Bs5FhNw2.d.cts → define-content-type-BUZeWtpk.d.cts} +3657 -2594
- package/dist/{include-paths-Cb7iPCW2.d.ts → define-content-type-DOrZb_a_.d.ts} +3657 -2594
- package/dist/{define-schema-UHViXKiv.d.ts → define-schema-5aHmsA8j.d.ts} +238 -356
- package/dist/{define-schema-sWJ8Zn7C.d.cts → define-schema-nCfZeboB.d.cts} +238 -356
- package/dist/{index-8DozAWsA.d.ts → index-B97ZDj44.d.ts} +2 -2
- package/dist/{index--_u1nPHv.d.cts → index-C-pcllqY.d.cts} +2 -2
- package/dist/index-DOhFQbv7.d.ts +3065 -0
- package/dist/index-DgVV5Nj7.d.cts +3065 -0
- package/dist/index.cjs +90 -130
- package/dist/index.d.cts +16 -23
- package/dist/index.d.ts +16 -23
- package/dist/index.js +4 -4
- package/dist/rich-text/index.cjs +11 -11
- package/dist/rich-text/index.d.cts +2 -2
- package/dist/rich-text/index.d.ts +2 -2
- package/dist/rich-text/index.js +1 -1
- package/dist/schema/index.cjs +33 -37
- package/dist/schema/index.d.cts +33 -18
- package/dist/schema/index.d.ts +33 -18
- package/dist/schema/index.js +1 -1
- package/dist/types/index.d.cts +1 -1
- package/dist/types/index.d.ts +1 -1
- package/package.json +2 -2
- package/dist/index-BT0L8xJj.d.cts +0 -3717
- 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 };
|