@relyio/sdk 0.12.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 +228 -0
- package/dist/{chunk-3DRDHXLY.js → chunk-5UMY6CS4.js} +435 -23
- package/dist/{chunk-FRJEUAAF.js → chunk-DBBZVZOS.js} +968 -2670
- package/dist/{chunk-DXCVO6M5.cjs → chunk-GUPV2M3X.cjs} +969 -2680
- package/dist/{chunk-POEEDYFM.js → chunk-PCFTJLEP.js} +543 -260
- package/dist/{chunk-FI5VIF4M.cjs → chunk-RSHMA4KT.cjs} +442 -22
- package/dist/{chunk-3MSFWXQL.cjs → chunk-X4KSS7PP.cjs} +542 -260
- package/dist/client/index.cjs +3 -3
- package/dist/client/index.d.cts +4 -5
- package/dist/client/index.d.ts +4 -5
- package/dist/client/index.js +1 -1
- package/dist/{include-paths-CL4gHv6I.d.cts → define-content-type-BUZeWtpk.d.cts} +4458 -1312
- package/dist/{include-paths-CrJNTsMl.d.ts → define-content-type-DOrZb_a_.d.ts} +4458 -1312
- package/dist/define-schema-5aHmsA8j.d.ts +699 -0
- package/dist/define-schema-nCfZeboB.d.cts +699 -0
- package/dist/index-B97ZDj44.d.ts +507 -0
- package/dist/index-C-pcllqY.d.cts +507 -0
- package/dist/index-DOhFQbv7.d.ts +3065 -0
- package/dist/index-DgVV5Nj7.d.cts +3065 -0
- package/dist/index.cjs +114 -147
- package/dist/index.d.cts +49 -87
- package/dist/index.d.ts +49 -87
- package/dist/index.js +5 -29
- package/dist/markdown/index.cjs +13750 -0
- package/dist/markdown/index.d.cts +111 -0
- package/dist/markdown/index.d.ts +111 -0
- package/dist/markdown/index.js +13742 -0
- package/dist/rich-text/index.cjs +26 -6
- package/dist/rich-text/index.d.cts +3 -148
- package/dist/rich-text/index.d.ts +3 -148
- package/dist/rich-text/index.js +1 -1
- package/dist/rich-text.d-BWc-5gX1.d.cts +326 -0
- package/dist/rich-text.d-BWc-5gX1.d.ts +326 -0
- package/dist/schema/index.cjs +33 -37
- package/dist/schema/index.d.cts +37 -191
- package/dist/schema/index.d.ts +37 -191
- package/dist/schema/index.js +1 -1
- package/dist/types/index.d.cts +2 -3
- package/dist/types/index.d.ts +2 -3
- package/package.json +14 -3
- package/dist/define-schema-DgctHlI2.d.cts +0 -1034
- package/dist/define-schema-mrEZbY1h.d.ts +0 -1034
- package/dist/index-B9RfZVF1.d.ts +0 -861
- package/dist/index-BrvEg6-A.d.cts +0 -3729
- package/dist/index-Pw0onkJe.d.ts +0 -3729
- package/dist/index-b-AkfTUv.d.cts +0 -861
- package/dist/rich-text.d-rKmpgdGd.d.cts +0 -200
- package/dist/rich-text.d-rKmpgdGd.d.ts +0 -200
|
@@ -0,0 +1,3065 @@
|
|
|
1
|
+
import { c$ as paths, d0 as MergedRelyIOConfig, cS as ApiResponse, cR as RequestConfig, aM as EntryMetadata, cs as BaseContent, ap as Reading, al as CheckedIncludes, d1 as ReadWhere, cx as WhereClause, aq as ReadWith, cD as UpdateInput, at as UntypedReading, cB as RevisedContent, cA as CreateInput, cC as RestoredContent, cl as SDKError, cY as BaseValidationError, cX as ValidationViolation, b2 as AssetServedBlocks, d2 as CheckedInclude, d3 as components, cq as AssetFileBlock, ct as BaseSingleton, cu as BaseSettingsGroup, aN as InferContentType, an as ContentReading, aT as InferSingleton, cm as RelyIOConfig, b0 as InferAssetType, ak as AssetReading, as as SingletonReading, aY as InferSettingsGroup, ar as SettingsReading, cn as RelyIOConfigUpdate } from './define-content-type-DOrZb_a_.js';
|
|
2
|
+
import { a as SchemaDefinition, b as SchemaContentTypeNames, j as SchemaEntryOf, f as SchemaLocaleCodes, c as SchemaSingletonNames, A as AggregateBlock, K as QueryParameterValue, F as FullTextSearch, e as SchemaQueryNames, J as InferQueryParams, L as AnyQueryParameters, h as SchemaAssetTypeNames, g as SchemaSettingsGroupNames } from './define-schema-5aHmsA8j.js';
|
|
3
|
+
import { VisitorResyncReason } from './live-visitor.js';
|
|
4
|
+
import { DevUnreadableReason } from './dev-handshake/index.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Where the SDK's transport gets its truth.
|
|
8
|
+
*
|
|
9
|
+
* Every route this client calls is named by its **path template as the
|
|
10
|
+
* committed document spells it**, and the body type it expects is read out of
|
|
11
|
+
* that document rather than written down here. `api-schema.ts` beside this file
|
|
12
|
+
* is generated by `pnpm api:types` from `apps/services/api/openapi-spec.json`,
|
|
13
|
+
* which is itself generated from that service's `RouteContract`s
|
|
14
|
+
* (`INV-API-DOCUMENT-IS-DERIVED`). So the chain is: one declaration in
|
|
15
|
+
* `routes.ts` → the serializer that produces the body → the document → these
|
|
16
|
+
* types → the method that reads it.
|
|
17
|
+
*
|
|
18
|
+
* That chain is the answer to the question this phase exists to close. Before
|
|
19
|
+
* it, six SDK methods read a key the server does not send; each had no caller
|
|
20
|
+
* and no test, so nothing could see them. Now:
|
|
21
|
+
*
|
|
22
|
+
* - A route that changes **kind** — `collection` to `object`, say — changes
|
|
23
|
+
* whether the document's 200 body carries `page` and whether `data` is an
|
|
24
|
+
* array. `Wire<…>` follows, `readPayload` follows, and the method's declared
|
|
25
|
+
* return type stops matching. `tsc` names the method.
|
|
26
|
+
* - A route that changes **path** or is deleted stops being a `keyof paths`, so
|
|
27
|
+
* the call site fails on the literal.
|
|
28
|
+
* - A route that changes **dialect** changes `page` from `CursorPage` to
|
|
29
|
+
* `OffsetPage`, and anything reading `nextCursor` fails.
|
|
30
|
+
*
|
|
31
|
+
* `/v1` lives in these templates because it lives in the document
|
|
32
|
+
* (`INV-API-VERSION-IS-DECIDED-ONCE`). Before this phase the SDK's base URL was
|
|
33
|
+
* `https://api.relyio.com` and its paths were `/{contentType}`, so every
|
|
34
|
+
* versioned request went to a path the router does not serve.
|
|
35
|
+
*
|
|
36
|
+
* Spec: `devtools/docs/guide/engineering/api-contract-spec.md`,
|
|
37
|
+
* `INV-API-CLIENT-IS-DERIVED`.
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
/** Every path the committed document declares. */
|
|
41
|
+
type ApiPath = keyof paths;
|
|
42
|
+
type Verb$1 = 'get' | 'post' | 'put' | 'patch' | 'delete';
|
|
43
|
+
/** The verbs the document declares on this path — not the seven HTTP has. */
|
|
44
|
+
type ApiMethod<P extends ApiPath> = {
|
|
45
|
+
[M in Verb$1]: M extends keyof paths[P] ? paths[P][M] extends {
|
|
46
|
+
responses: unknown;
|
|
47
|
+
} ? M : never : never;
|
|
48
|
+
}[Verb$1];
|
|
49
|
+
type Operation<P extends ApiPath, M extends ApiMethod<P>> = M extends keyof paths[P] ? paths[P][M] : never;
|
|
50
|
+
type JsonBody<R> = R extends {
|
|
51
|
+
content: {
|
|
52
|
+
'application/json': infer B;
|
|
53
|
+
};
|
|
54
|
+
} ? B : never;
|
|
55
|
+
/**
|
|
56
|
+
* The success body the document declares, preferring 200 where a route serves
|
|
57
|
+
* both. A route with neither — `kind: 'empty'`, which serves 204 — resolves to
|
|
58
|
+
* `never`, and `readPayload` cannot be called on it.
|
|
59
|
+
*/
|
|
60
|
+
type ApiSuccessBody<P extends ApiPath, M extends ApiMethod<P>> = Operation<P, M> extends {
|
|
61
|
+
responses: infer R;
|
|
62
|
+
} ? 200 extends keyof R ? JsonBody<R[200]> : 201 extends keyof R ? JsonBody<R[201]> : never : never;
|
|
63
|
+
/**
|
|
64
|
+
* The document's success body with the payload typed as the caller's `T`.
|
|
65
|
+
*
|
|
66
|
+
* The array arm is tested **first** on purpose: `{ data: unknown[] }` also
|
|
67
|
+
* satisfies `{ data: unknown }`, so the object arm would swallow every
|
|
68
|
+
* collection and hand a list route's caller a single entity — which is the
|
|
69
|
+
* exact defect class (`ContentClient.children` reading `.data.items` from an
|
|
70
|
+
* object route) that this phase makes unrepresentable.
|
|
71
|
+
*/
|
|
72
|
+
type Wire<P extends ApiPath, M extends ApiMethod<P>, T> = ApiSuccessBody<P, M> extends {
|
|
73
|
+
data: unknown[];
|
|
74
|
+
} ? Omit<ApiSuccessBody<P, M>, 'data'> & {
|
|
75
|
+
data: T[];
|
|
76
|
+
} : ApiSuccessBody<P, M> extends {
|
|
77
|
+
data: unknown;
|
|
78
|
+
} ? Omit<ApiSuccessBody<P, M>, 'data'> & {
|
|
79
|
+
data: T;
|
|
80
|
+
} : ApiSuccessBody<P, M>;
|
|
81
|
+
/**
|
|
82
|
+
* The path parameters the document declares for this operation, or `never`
|
|
83
|
+
* where it declares none.
|
|
84
|
+
*
|
|
85
|
+
* `never` rather than `{}`: a route with no segments must **forbid** a `path`
|
|
86
|
+
* argument, not merely tolerate an empty one, and `keyof Record<string, never>`
|
|
87
|
+
* is `string`, so an empty-object encoding cannot be told apart from a real
|
|
88
|
+
* one at the type level.
|
|
89
|
+
*/
|
|
90
|
+
type PathParamsOf<P extends ApiPath, M extends ApiMethod<P>> = Operation<P, M> extends {
|
|
91
|
+
parameters: {
|
|
92
|
+
path?: infer X;
|
|
93
|
+
};
|
|
94
|
+
} ? [X] extends [undefined] ? never : NonNullable<X> : never;
|
|
95
|
+
/**
|
|
96
|
+
* The query parameters the document declares for this operation, or `never` where it declares
|
|
97
|
+
* none (REL-687).
|
|
98
|
+
*
|
|
99
|
+
* The service declares them beside each route — a query DTO's whitelisted properties, the names a
|
|
100
|
+
* handler reads, `?_fresh` on a read the edge keeps — and `apps/services/api`'s
|
|
101
|
+
* `contract/query-parameters.spec.ts` holds the document to what each handler reads. So a name the
|
|
102
|
+
* API stops reading stops compiling here, and a name it refuses — `offset` on the entry list, which
|
|
103
|
+
* the SDK once sent on every `.offset()` and the API answered with a 400 — never compiled at all.
|
|
104
|
+
*
|
|
105
|
+
* `never` for the same reason {@link PathParamsOf} uses it: a route with no query must forbid one.
|
|
106
|
+
*/
|
|
107
|
+
type QueryOf<P extends ApiPath, M extends ApiMethod<P>> = Operation<P, M> extends {
|
|
108
|
+
parameters: {
|
|
109
|
+
query?: infer Q;
|
|
110
|
+
};
|
|
111
|
+
} ? [Q] extends [undefined] ? never : NonNullable<Q> : never;
|
|
112
|
+
/** What {@link matchRoute} recovers from a URL the client emitted. */
|
|
113
|
+
interface RouteMatch {
|
|
114
|
+
/** The document's path template — the value a tag translator switches on. */
|
|
115
|
+
path: ApiPath;
|
|
116
|
+
/** The template's `{name}` segments, decoded: the inverse of {@link fillPath}. */
|
|
117
|
+
params: Record<string, string>;
|
|
118
|
+
/** The query string; a key that appears more than once comes back as an array. */
|
|
119
|
+
query: Record<string, string | string[]>;
|
|
120
|
+
}
|
|
121
|
+
interface MatchRouteOptions {
|
|
122
|
+
/**
|
|
123
|
+
* The client's `baseUrl`, when it carries a path prefix of its own
|
|
124
|
+
* (`https://example.com/relyio`). Its path is removed before the template is
|
|
125
|
+
* matched. Unneeded for the default `https://api.relyio.com`.
|
|
126
|
+
*/
|
|
127
|
+
baseUrl?: string;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Recover the route a URL names: the inverse of {@link fillPath}, over the same
|
|
131
|
+
* committed document (`INV-API-CLIENT-IS-DERIVED`).
|
|
132
|
+
*
|
|
133
|
+
* `matchRoute(url).path` is an `ApiPath`, so anything that keys behaviour on a
|
|
134
|
+
* request — a cache-tag translator, say — switches on the document's templates
|
|
135
|
+
* and never on a route table of its own.
|
|
136
|
+
*
|
|
137
|
+
* Returns `null` for a URL no declared template matches — a presigned storage
|
|
138
|
+
* URL, or a path outside `baseUrl` — rather than a guess.
|
|
139
|
+
*/
|
|
140
|
+
declare function matchRoute(url: string | URL, options?: MatchRouteOptions): RouteMatch | null;
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Dev-mode detection for `relyio dev` sandboxes — the SDK's I/O shell over `../dev-handshake`.
|
|
144
|
+
*
|
|
145
|
+
* Every rule about the handshake — the path, the env names, the required fields, the deadline —
|
|
146
|
+
* lives in `@relyio/sdk/dev-handshake`, which `relyio dev` imports too. What is left here is the
|
|
147
|
+
* part that is genuinely the SDK's: reading env and walking the filesystem from the cwd, both of
|
|
148
|
+
* which have to happen without a static `node:*` import so the module stays browser-safe.
|
|
149
|
+
*
|
|
150
|
+
* Node access goes through `process.getBuiltinModule` (Node ≥ 20.16 / 22, and the platform targets
|
|
151
|
+
* Node 24), which is absent in browsers and edge runtimes — so the resolver is inert everywhere it
|
|
152
|
+
* should be, and a bundler never has to shim `fs`.
|
|
153
|
+
*
|
|
154
|
+
* **Order: env, then file.** The wrapped-child env is the primary carrier and wins. Before REL-647
|
|
155
|
+
* it won *unconditionally*, because it had no deadline to lose on; it now goes through the same
|
|
156
|
+
* rule the file does.
|
|
157
|
+
*/
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* What the handshake says about the sandbox, as the preview toolbar displays it
|
|
161
|
+
* (`sdk-surfaces-spec.md` decision 22). `TypedRelyIOClient.sandbox` returns it.
|
|
162
|
+
*
|
|
163
|
+
* **Plain data, on purpose.** In Next the toolbar is rendered from a server layout, and a client
|
|
164
|
+
* cannot cross the RSC boundary — while the browser cannot read the handshake at all, because this
|
|
165
|
+
* module is inert without `process.getBuiltinModule`. So the server-created client's `sandbox` is the
|
|
166
|
+
* prop the toolbar is handed, and it has to survive `JSON` unchanged: strings and `null`, dates as
|
|
167
|
+
* the ISO strings the handshake carried, no class instances.
|
|
168
|
+
*
|
|
169
|
+
* **No key.** The handshake also carries the sandbox's preview key; that is the client's credential,
|
|
170
|
+
* held beside this value in {@link DevResolution} and never inside it, so handing the state around
|
|
171
|
+
* does not hand the key with it.
|
|
172
|
+
*
|
|
173
|
+
* - `active` — the client targets the sandbox. `branchId` is `null` from the env carrier, which names
|
|
174
|
+
* no branch; only `.relyio/dev.json` does. `expiresAt` is `null` for an unbounded sandbox.
|
|
175
|
+
* - `expired` / `unreadable` — a handshake was found and refused; the client targets the configured
|
|
176
|
+
* dataset and key, and `mergeConfig` has said so on the console.
|
|
177
|
+
* - `absent` — no handshake on either carrier. This is also what a browser or edge runtime reads,
|
|
178
|
+
* since neither can see the env or the file.
|
|
179
|
+
* - `disabled` — detection did not run: `dev: false` was passed, or `NODE_ENV === 'production'`
|
|
180
|
+
* and `dev: true` was not.
|
|
181
|
+
*/
|
|
182
|
+
type SandboxState = {
|
|
183
|
+
state: 'active';
|
|
184
|
+
datasetId: string;
|
|
185
|
+
branchId: string | null;
|
|
186
|
+
expiresAt: string | null;
|
|
187
|
+
source: 'env' | 'file';
|
|
188
|
+
} | {
|
|
189
|
+
state: 'expired';
|
|
190
|
+
expiredAt: string;
|
|
191
|
+
} | {
|
|
192
|
+
state: 'unreadable';
|
|
193
|
+
reason: DevUnreadableReason;
|
|
194
|
+
} | {
|
|
195
|
+
state: 'absent';
|
|
196
|
+
} | {
|
|
197
|
+
state: 'disabled';
|
|
198
|
+
};
|
|
199
|
+
/** The sandbox the client targets, once a carrier has been accepted. */
|
|
200
|
+
type ActiveSandbox = Extract<SandboxState, {
|
|
201
|
+
state: 'active';
|
|
202
|
+
}>;
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* What a call site supplies beside the route it named.
|
|
206
|
+
*
|
|
207
|
+
* `path` is required exactly when the document's template has segments, and
|
|
208
|
+
* forbidden when it does not — so a call that forgot to scope a request, or one
|
|
209
|
+
* that scoped a request the route does not scope, is a compile error rather
|
|
210
|
+
* than a URL with a literal `{id}` in it.
|
|
211
|
+
*/
|
|
212
|
+
type SendOptions<P extends ApiPath, M extends ApiMethod<P>> = {
|
|
213
|
+
/**
|
|
214
|
+
* Query string values: exactly the names the document declares for this route, at the types it
|
|
215
|
+
* declares them (`QueryOf`, REL-687). An undeclared name is a compile error, not a 400.
|
|
216
|
+
*/
|
|
217
|
+
query?: QueryOf<P, M>;
|
|
218
|
+
body?: unknown;
|
|
219
|
+
/** Request headers this call adds, e.g. `If-Match` / `Idempotency-Key` (API4). */
|
|
220
|
+
headers?: Record<string, string>;
|
|
221
|
+
signal?: AbortSignal;
|
|
222
|
+
timeout?: number;
|
|
223
|
+
} & ([PathParamsOf<P, M>] extends [never] ? {
|
|
224
|
+
path?: never;
|
|
225
|
+
} : {
|
|
226
|
+
path: PathParamsOf<P, M>;
|
|
227
|
+
});
|
|
228
|
+
/**
|
|
229
|
+
* Core HTTP client for the RelyIO SDK.
|
|
230
|
+
*
|
|
231
|
+
* `send` is **the** typed entry: it names a route by the path template the
|
|
232
|
+
* committed document declares, and returns that operation's body type with the
|
|
233
|
+
* payload typed as `T`. `readPayload` from `@relyio/api-contract/client` is
|
|
234
|
+
* then the one unwrap — there is no second spelling and no runtime test of
|
|
235
|
+
* which envelope arrived (`INV-API-CLIENT-IS-DERIVED`).
|
|
236
|
+
*
|
|
237
|
+
* The verb helpers below stay for the escape hatch `getHttpClient()` exposes;
|
|
238
|
+
* nothing inside the SDK uses them.
|
|
239
|
+
*/
|
|
240
|
+
declare class HttpClient {
|
|
241
|
+
/** What every request reads off the configuration, derived by {@link configure} and nowhere else. */
|
|
242
|
+
private connection;
|
|
243
|
+
/**
|
|
244
|
+
* Set on a view {@link withHeaders} made: the client every request is sent through, and the
|
|
245
|
+
* headers laid beneath each request's own. A view keeps no connection of its own that matters —
|
|
246
|
+
* everything it is asked to change, it changes on that client.
|
|
247
|
+
*/
|
|
248
|
+
private view?;
|
|
249
|
+
constructor(config: MergedRelyIOConfig);
|
|
250
|
+
/**
|
|
251
|
+
* A view of this client that sends every request through it with `headers` added — beneath the
|
|
252
|
+
* request's own, over this client's — and changes nothing else: its key, base URL, timeout,
|
|
253
|
+
* retries and `fetch` are this client's as they are when each request is sent, so a later
|
|
254
|
+
* `configure()` or `setApiKey()` on this client reaches the view, and one on the view reaches
|
|
255
|
+
* this client. `client.with({ dataset })` is one (REL-2417).
|
|
256
|
+
*/
|
|
257
|
+
withHeaders(headers: Readonly<Record<string, string>>): HttpClient;
|
|
258
|
+
/**
|
|
259
|
+
* Run on `config` from now on: every later request — whoever holds this client — is sent to its
|
|
260
|
+
* `baseUrl`, with its headers, key and dataset, its timeout and retries, through its `fetch`, and
|
|
261
|
+
* logged by its `debug` options.
|
|
262
|
+
*
|
|
263
|
+
* The client's own `updateConfig()` calls this rather than building a second client, so a
|
|
264
|
+
* `content()` or other sub-client taken before the update sends through the update too. Anything
|
|
265
|
+
* set on this client since it was last configured — `setDefaultHeaders()`, `setBaseUrl()` — is
|
|
266
|
+
* replaced by what `config` says.
|
|
267
|
+
*/
|
|
268
|
+
configure(config: MergedRelyIOConfig): void;
|
|
269
|
+
private announce;
|
|
270
|
+
/**
|
|
271
|
+
* Call a route the committed document declares, and get back the body that
|
|
272
|
+
* document says it serves.
|
|
273
|
+
*/
|
|
274
|
+
send<P extends ApiPath, M extends ApiMethod<P>, T = unknown>(path: P, method: M, options: SendOptions<P, M>): Promise<ApiResponse<Wire<P, M, T>>>;
|
|
275
|
+
/**
|
|
276
|
+
* Call a route and return its payload — `send` plus the one unwrap, which is
|
|
277
|
+
* what all but a handful of call sites want.
|
|
278
|
+
*/
|
|
279
|
+
fetchPayload<P extends ApiPath, M extends ApiMethod<P>, T = unknown>(path: P, method: M, options: SendOptions<P, M>): Promise<Wire<P, M, T> extends {
|
|
280
|
+
data: unknown;
|
|
281
|
+
} ? Wire<P, M, T>['data'] : never>;
|
|
282
|
+
/**
|
|
283
|
+
* Make an HTTP request, retrying only where a retry is safe.
|
|
284
|
+
*
|
|
285
|
+
* The headers are merged **once**, before the first attempt, and every attempt
|
|
286
|
+
* sends that same object. That is what makes a minted `Idempotency-Key` stable
|
|
287
|
+
* across the retries of one logical write: the service sees one key and
|
|
288
|
+
* answers the replay from the original outcome instead of applying it twice.
|
|
289
|
+
*
|
|
290
|
+
* The loop always makes one attempt. The incumbent's `while (attempt <
|
|
291
|
+
* maxAttempts)` made none at `retryAttempts: 0` — a value the config validator
|
|
292
|
+
* then accepted, and now refuses — and then dereferenced an unassigned
|
|
293
|
+
* `lastError`. `retryAttempts` is the most attempts, the first included; an
|
|
294
|
+
* `HttpClient` constructed directly is not validated, so the first attempt
|
|
295
|
+
* stays unconditional.
|
|
296
|
+
*
|
|
297
|
+
* A request that would carry no `X-API-Key` makes none: it rejects with
|
|
298
|
+
* `MissingApiKeyError` before `fetch` is called, is not retried, and names
|
|
299
|
+
* `apiKey`. This is where a client built without a key learns it — the
|
|
300
|
+
* constructor accepts one so that a module-scope client can be evaluated by a
|
|
301
|
+
* build that holds no key (REL-1120) — and so does one whose `setApiKey` was
|
|
302
|
+
* given none.
|
|
303
|
+
*/
|
|
304
|
+
request<T = unknown>(config: RequestConfig): Promise<ApiResponse<T>>;
|
|
305
|
+
/**
|
|
306
|
+
* Make a GET request
|
|
307
|
+
*/
|
|
308
|
+
get<T = unknown>(url: string, params?: Record<string, unknown>): Promise<ApiResponse<T>>;
|
|
309
|
+
/**
|
|
310
|
+
* Make a POST request
|
|
311
|
+
*/
|
|
312
|
+
post<T = unknown>(url: string, data?: unknown, params?: Record<string, unknown>): Promise<ApiResponse<T>>;
|
|
313
|
+
/**
|
|
314
|
+
* Make a PUT request
|
|
315
|
+
*/
|
|
316
|
+
put<T = unknown>(url: string, data?: unknown, params?: Record<string, unknown>): Promise<ApiResponse<T>>;
|
|
317
|
+
/**
|
|
318
|
+
* Make a DELETE request
|
|
319
|
+
*/
|
|
320
|
+
delete<T = unknown>(url: string, params?: Record<string, unknown>): Promise<ApiResponse<T>>;
|
|
321
|
+
/**
|
|
322
|
+
* Make a PATCH request
|
|
323
|
+
*/
|
|
324
|
+
patch<T = unknown>(url: string, data?: unknown, params?: Record<string, unknown>): Promise<ApiResponse<T>>;
|
|
325
|
+
/**
|
|
326
|
+
* Add headers to every later request, each replacing any header of the same name — the name
|
|
327
|
+
* matched without regard to case — until the client is next configured.
|
|
328
|
+
*/
|
|
329
|
+
setDefaultHeaders(headers: Record<string, string>): void;
|
|
330
|
+
/**
|
|
331
|
+
* Replace the key every later request sends as `X-API-Key`, and the `apiKey` the configuration
|
|
332
|
+
* reports.
|
|
333
|
+
*
|
|
334
|
+
* The key is read the way construction reads it (`presentKey`): an absent, empty or blank key is
|
|
335
|
+
* no key, so the header is dropped and the next request is refused with `MissingApiKeyError`
|
|
336
|
+
* before anything is sent. A key header of any other spelling — one given in `headers` — goes
|
|
337
|
+
* too, from the headers sent and from the headers the configuration reports, so the old key is
|
|
338
|
+
* never sent beside the new one.
|
|
339
|
+
*
|
|
340
|
+
* The configuration is the one the owning `TypedRelyIOClient` holds (it hands its own to the
|
|
341
|
+
* constructor), so its `getConfig().apiKey` — what a surface reads to decide a key's kind — is the
|
|
342
|
+
* key sent from here on.
|
|
343
|
+
*
|
|
344
|
+
* This replaced `setAuthToken`, which added `Authorization: Bearer` beside an `X-API-Key` it
|
|
345
|
+
* never touched: the API reads `X-API-Key` first, so the old key was the one used (REL-1385).
|
|
346
|
+
*/
|
|
347
|
+
setApiKey(apiKey: string): void;
|
|
348
|
+
/**
|
|
349
|
+
* Get current default headers
|
|
350
|
+
*/
|
|
351
|
+
getDefaultHeaders(): Record<string, string>;
|
|
352
|
+
/**
|
|
353
|
+
* Update base URL
|
|
354
|
+
*/
|
|
355
|
+
setBaseUrl(baseUrl: string): void;
|
|
356
|
+
/**
|
|
357
|
+
* Get current base URL
|
|
358
|
+
*/
|
|
359
|
+
getBaseUrl(): string;
|
|
360
|
+
/**
|
|
361
|
+
* One attempt.
|
|
362
|
+
*
|
|
363
|
+
* The caller's `signal` and the timeout both end it, through one controller.
|
|
364
|
+
* The incumbent handed `fetch` the caller's signal **instead of** its own, so a
|
|
365
|
+
* request that passed a signal had no timeout at all — and a caller abort then
|
|
366
|
+
* surfaced as `Request timed out`. `timedOut` is what tells the two apart: a
|
|
367
|
+
* timeout is a `TimeoutError`, a caller abort is the platform's `AbortError`,
|
|
368
|
+
* rethrown as it came.
|
|
369
|
+
*/
|
|
370
|
+
private makeRequest;
|
|
371
|
+
/**
|
|
372
|
+
* The headers without those a browser controls itself, when this is a browser; unchanged when it
|
|
373
|
+
* is not.
|
|
374
|
+
*
|
|
375
|
+
* A name is matched without regard to case (RFC 9110 §5.1), because a browser refuses `Origin`
|
|
376
|
+
* whatever case a page spells it in. This matched only the all-lowercase and all-uppercase
|
|
377
|
+
* spellings, so `Origin` and `User-Agent` — how people write them — reached `fetch` (REL-1409).
|
|
378
|
+
*/
|
|
379
|
+
private filterHeadersForBrowser;
|
|
380
|
+
/**
|
|
381
|
+
* Prepare request body based on content type and environment
|
|
382
|
+
*/
|
|
383
|
+
private prepareRequestBody;
|
|
384
|
+
private parseResponse;
|
|
385
|
+
private resolveUrl;
|
|
386
|
+
private mergeRequestConfig;
|
|
387
|
+
private buildQueryString;
|
|
388
|
+
private headersToObject;
|
|
389
|
+
private log;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* API4's two request headers, as the SDK's writes send them.
|
|
394
|
+
*
|
|
395
|
+
* `Idempotency-Key` is an option on every write whose route declares it (`idempotencyKey`), and the
|
|
396
|
+
* SDK mints one when none is given. `If-Match` is never an option: a checked write builds it from
|
|
397
|
+
* the entry it was read as (`versionCheckOf`), from the `_metadata` every read carries in its body
|
|
398
|
+
* whether or not the edge cache served it (REL-2418). The SDK does not read the separate
|
|
399
|
+
* `X-Entity-Tag` header, which the edge removes from what it stores; the API keeps sending it for
|
|
400
|
+
* callers using plain HTTP (REL-2427). A batch operation carries its check in its own body, as
|
|
401
|
+
* `ifMatch` (`batch/batch-operations.ts`, REL-2421).
|
|
402
|
+
*/
|
|
403
|
+
interface RetryableWriteOptions {
|
|
404
|
+
/**
|
|
405
|
+
* A key that makes a retry of **this** write safe.
|
|
406
|
+
*
|
|
407
|
+
* A replay inside the service's window returns the original outcome rather
|
|
408
|
+
* than applying twice. The service derives that response from committed state
|
|
409
|
+
* pinned to the recorded version — it does not store bytes — so a replay after
|
|
410
|
+
* the version has been pruned is refused `RESOURCE_EXPIRED` rather than
|
|
411
|
+
* answered from a stale copy.
|
|
412
|
+
*
|
|
413
|
+
* **The SDK mints one when you do not.** It used to be caller-supplied and
|
|
414
|
+
* nothing else, which meant the default write carried no key while the retry
|
|
415
|
+
* loop replayed it anyway — the one mechanism that makes a replay safe was
|
|
416
|
+
* optional on the one path that needed it. Supply your own to make a key
|
|
417
|
+
* outlive the process: a job that may be restarted wants a key derived from
|
|
418
|
+
* the work, not one minted per call.
|
|
419
|
+
*/
|
|
420
|
+
idempotencyKey?: string;
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* An entry a read handed back, as much of it as a version check needs: its `_metadata`'s id, the
|
|
424
|
+
* locale it was read in and the two counters it was read at (REL-2418).
|
|
425
|
+
*
|
|
426
|
+
* Every read carries these in its body, whether origin or the edge cache served it — the edge
|
|
427
|
+
* removes only the separate `X-Entity-Tag` header from what it stores, so a check taken from that
|
|
428
|
+
* header vanished after a cached read. A `Pick` of the document's `EntryMetadata`, so a counter the
|
|
429
|
+
* API stops reporting stops compiling here.
|
|
430
|
+
*/
|
|
431
|
+
interface ReadEntry {
|
|
432
|
+
readonly _metadata: Pick<EntryMetadata, 'id' | 'locale' | 'version' | 'sharedVersion'>;
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* The version check a write takes from the entry it was read as: the entry's id, the locale it was
|
|
436
|
+
* read in — which the write addresses, so a read negotiated from `Accept-Language` is written back
|
|
437
|
+
* to that locale — and the `If-Match` tag over its two counters, spelled as the API mints it.
|
|
438
|
+
*/
|
|
439
|
+
declare function versionCheckOf(entry: ReadEntry): {
|
|
440
|
+
id: string;
|
|
441
|
+
locale: string;
|
|
442
|
+
ifMatch: string;
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* The client's locale as a sub-client holds it: a function it asks on every call, or a locale fixed
|
|
447
|
+
* when it was built.
|
|
448
|
+
*
|
|
449
|
+
* `TypedRelyIOClient` hands every sub-client and builder a function over the configuration it holds,
|
|
450
|
+
* so one taken before `updateConfig({ locale })` sends the locale the client holds when the call is
|
|
451
|
+
* made, and none once `updateConfig({ locale: null })` has cleared it (REL-1453). A string is a locale
|
|
452
|
+
* fixed at construction — a handle built directly, as the tests build one — and stays what it was.
|
|
453
|
+
*/
|
|
454
|
+
type ClientLocale = string | (() => string | undefined);
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Two read terms every read that takes them writes the same way: the children an entry embeds,
|
|
458
|
+
* and the refusal of a condition on `_metadata`. Here once, for the list and entry reads
|
|
459
|
+
* (`read-options.ts`), the search family and the query call alike.
|
|
460
|
+
*/
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* Children population options: how deep, and how many per parent.
|
|
464
|
+
*
|
|
465
|
+
* The API expands an entry's children of **every** content type (`?children=true|1|2|3`); there is
|
|
466
|
+
* no filter by type. The option that used to name types was sent as `?children=<type>,<type>`,
|
|
467
|
+
* which the API parsed as no expansion at all, and `depth` was never sent (REL-1118).
|
|
468
|
+
*/
|
|
469
|
+
interface ChildrenOptions {
|
|
470
|
+
/** Levels of children to embed: 1 (the default) to 3 */
|
|
471
|
+
depth?: 1 | 2 | 3;
|
|
472
|
+
/** Maximum children per parent: 1 to 50, default 25 */
|
|
473
|
+
limit?: number;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* The options a content type's reads take, and the one place each becomes the query its route
|
|
478
|
+
* declares (REL-2415, REL-2417; REL-2409 decisions Q3, Q5, Q6, Q9, Q10, Q11, Q12, Q14, Q15 and Q16).
|
|
479
|
+
*
|
|
480
|
+
* Every read takes **one options object**, with one name per concern. Each option that becomes a
|
|
481
|
+
* query term is typed from the query the committed document declares for its route (`QueryOf`,
|
|
482
|
+
* REL-687), and {@link listQuery} / {@link entryQuery} return that query type — so an option whose
|
|
483
|
+
* term the API stops reading stops compiling here, and a term the API does not read (`offset`,
|
|
484
|
+
* refused with a 400 on every list) cannot be offered. Two options are headers, which no route
|
|
485
|
+
* declares: `dataset` (`X-RelyIO-Dataset`) and `fresh: true` (`Cache-Control: no-cache`), written
|
|
486
|
+
* by {@link readHeaders}.
|
|
487
|
+
*
|
|
488
|
+
* `pageSize` is not offered: `limit` is the page size of a cursor read and of a page-number read
|
|
489
|
+
* alike (`listPageLimit` in `@relyio/content-db` reads `limit` first).
|
|
490
|
+
*
|
|
491
|
+
* `select` and `sort` are a list's alone: `GET /v1/{contentType}/{id}` declares neither `fields`
|
|
492
|
+
* nor `sort`, so `get` offers neither.
|
|
493
|
+
*/
|
|
494
|
+
|
|
495
|
+
/** One declared term's value, as the document types it. */
|
|
496
|
+
type Term<P extends ApiPath, M extends ApiMethod<P>, K extends keyof QueryOf<P, M>> = QueryOf<P, M>[K];
|
|
497
|
+
/**
|
|
498
|
+
* What `list` answers: the API's own `{ data, page }`, the page in the cursor dialect —
|
|
499
|
+
* `{ nextCursor, hasMore }` and no total, which the dialect cannot carry (Q10).
|
|
500
|
+
*/
|
|
501
|
+
type EntryList<E> = Wire<'/v1/{contentType}', 'get', E>;
|
|
502
|
+
/**
|
|
503
|
+
* The Fields an entry of `T` declares, by key: what `select` picks and `sort` orders by. Any name
|
|
504
|
+
* where `T` declares none — a client no schema types — since the API alone knows which exist.
|
|
505
|
+
*/
|
|
506
|
+
type FieldKey<T> = [Exclude<keyof T, '_metadata'>] extends [never] ? string : Exclude<keyof T, '_metadata'> & string;
|
|
507
|
+
/**
|
|
508
|
+
* The entry's own dates a list can be ordered by beside its Fields: the system keys the list route
|
|
509
|
+
* documents as indexed (`GetEntriesDto.sort`), which `@relyio/content-db` reads at the top level of
|
|
510
|
+
* the entry rather than under its fields.
|
|
511
|
+
*/
|
|
512
|
+
type SystemSortKey = 'createdAt' | 'updatedAt' | 'publishedAt' | 'firstPublishedAt';
|
|
513
|
+
/**
|
|
514
|
+
* How a list is ordered: a direction per key, the **key order** the priority —
|
|
515
|
+
* `{ featured: 'desc', publishedAt: 'desc' }` orders by `featured` and breaks its ties by
|
|
516
|
+
* `publishedAt` (Q14). Each key is a Field `T` declares or a {@link SystemSortKey}.
|
|
517
|
+
*/
|
|
518
|
+
type SortOption<T> = {
|
|
519
|
+
readonly [K in FieldKey<T> | SystemSortKey]?: 'asc' | 'desc';
|
|
520
|
+
};
|
|
521
|
+
/**
|
|
522
|
+
* The locale codes a read of `R` may name: the codes the schema declares, or any where no schema
|
|
523
|
+
* types the read.
|
|
524
|
+
*/
|
|
525
|
+
type ReadLocale<R extends Reading> = R extends {
|
|
526
|
+
readonly locale: infer C extends string;
|
|
527
|
+
} ? C : string;
|
|
528
|
+
/**
|
|
529
|
+
* What `locale` takes (Q16): one code; a list of codes, the first answered at the top level and
|
|
530
|
+
* the others under `_metadata.localizations`; or `'*'`, every locale the entry has. Each code is
|
|
531
|
+
* one the schema declares.
|
|
532
|
+
*/
|
|
533
|
+
type LocaleOption<C extends string> = C | '*' | readonly [C, ...C[]];
|
|
534
|
+
/**
|
|
535
|
+
* How fresh an answer must be:
|
|
536
|
+
*
|
|
537
|
+
* - a feed position (`'<cursor>'`, a change's `fresh`) — no answer older than that change, which the
|
|
538
|
+
* edge still caches under that position (the sdk-surfaces spec's meaning). Live refetches use it;
|
|
539
|
+
* - `true` — past every cache, to origin, with `Cache-Control: no-cache`. For write flows, a re-read
|
|
540
|
+
* after a conflict; never for live refetches, which would send every viewer to origin at once.
|
|
541
|
+
*/
|
|
542
|
+
type FreshOption = true | NonNullable<Term<'/v1/{contentType}', 'get', '_fresh'>>;
|
|
543
|
+
/**
|
|
544
|
+
* The options every entry read takes, beside how a list pages and what it keeps.
|
|
545
|
+
*
|
|
546
|
+
* - `include` — the references to expand, each path checked against the type (REL-1801); the read's
|
|
547
|
+
* type follows the paths.
|
|
548
|
+
* - `locale` — {@link LocaleOption}; replaces the client's locale for this read.
|
|
549
|
+
* - `dataset` — the dataset this one read is of, by id or name; the client's otherwise.
|
|
550
|
+
* - `fresh` — {@link FreshOption}.
|
|
551
|
+
* - `children` — each entry's children, of every content type, under `_children`: `true` for one
|
|
552
|
+
* level, or a depth of 1 to 3 and at most `limit` per parent (REL-1118).
|
|
553
|
+
*/
|
|
554
|
+
interface EntryReadOptions<R extends Reading, P extends readonly string[], L extends LocaleOption<ReadLocale<R>> | undefined = undefined> {
|
|
555
|
+
/** Include paths: the references to expand into the entries they point at (REL-1801). */
|
|
556
|
+
readonly include?: CheckedIncludes<R, P>;
|
|
557
|
+
/** The locale, locales or `'*'` to read, over the client's locale (Q16). */
|
|
558
|
+
readonly locale?: L;
|
|
559
|
+
/** The dataset to read, by id or name, over the client's (Q9). */
|
|
560
|
+
readonly dataset?: string;
|
|
561
|
+
/** How fresh the answer must be: a feed position, or `true` for past every cache. */
|
|
562
|
+
readonly fresh?: FreshOption;
|
|
563
|
+
/** Embed each entry's children: `true`, or a depth and a per-parent limit (REL-1118). */
|
|
564
|
+
readonly children?: true | ChildrenOptions;
|
|
565
|
+
}
|
|
566
|
+
/** The options a list read takes beside how it pages: what it keeps, how, and how many a page holds. */
|
|
567
|
+
interface ListReadOptions<T extends BaseContent, R extends Reading, P extends readonly string[], S extends readonly FieldKey<T>[] | undefined = undefined, L extends LocaleOption<ReadLocale<R>> | undefined = undefined> extends EntryReadOptions<R, P, L> {
|
|
568
|
+
/**
|
|
569
|
+
* The conditions an entry must meet, by the paths the API filters on (REL-2136). A condition on
|
|
570
|
+
* `_metadata` is refused before anything is sent.
|
|
571
|
+
*/
|
|
572
|
+
readonly where?: ReadWhere<WhereClause<T>, R>;
|
|
573
|
+
/** How many entries a page holds, whether it is read by cursor or by number. */
|
|
574
|
+
readonly limit?: Term<'/v1/{contentType}', 'get', 'limit'>;
|
|
575
|
+
/**
|
|
576
|
+
* The entry's own Fields to return, beside `_metadata` and every Field an include path starts
|
|
577
|
+
* at (Q15). An expanded reference's Fields are picked in its include path:
|
|
578
|
+
* `include: ['author(name)']`.
|
|
579
|
+
*/
|
|
580
|
+
readonly select?: S;
|
|
581
|
+
/** The order, by Field or system date, the key order the priority (Q14). */
|
|
582
|
+
readonly sort?: SortOption<T>;
|
|
583
|
+
}
|
|
584
|
+
/**
|
|
585
|
+
* Which page: the cursor a page handed back (`page.nextCursor`), or a page number (1-based) for an
|
|
586
|
+
* archive page like `/blog/page/3` — one or the other, never both (Q6).
|
|
587
|
+
*/
|
|
588
|
+
type Paging = {
|
|
589
|
+
readonly cursor?: Term<'/v1/{contentType}', 'get', 'cursor'>;
|
|
590
|
+
readonly page?: never;
|
|
591
|
+
} | {
|
|
592
|
+
readonly page?: Term<'/v1/{contentType}', 'get', 'page'>;
|
|
593
|
+
readonly cursor?: never;
|
|
594
|
+
};
|
|
595
|
+
/** What `list(options)` takes. */
|
|
596
|
+
type ListOptions<T extends BaseContent, R extends Reading, P extends readonly string[], S extends readonly FieldKey<T>[] | undefined = undefined, L extends LocaleOption<ReadLocale<R>> | undefined = undefined> = ListReadOptions<T, R, P, S, L> & Paging;
|
|
597
|
+
/** What `listAll(options)` takes: a list's options, paged by the walk itself. */
|
|
598
|
+
type ListAllOptions<T extends BaseContent, R extends Reading, P extends readonly string[], S extends readonly FieldKey<T>[] | undefined = undefined, L extends LocaleOption<ReadLocale<R>> | undefined = undefined> = ListReadOptions<T, R, P, S, L>;
|
|
599
|
+
/** What `get(idOrSlug, options)` takes. */
|
|
600
|
+
type GetOptions<R extends Reading, P extends readonly string[], L extends LocaleOption<ReadLocale<R>> | undefined = undefined> = EntryReadOptions<R, P, L>;
|
|
601
|
+
/**
|
|
602
|
+
* What a one-document read takes — `singleton(key).get(options)` and `settings(group).get(options)`
|
|
603
|
+
* (REL-2419, REL-2409 decision Q18): a content read's options where their routes declare them.
|
|
604
|
+
*
|
|
605
|
+
* - `include`, `dataset` and `fresh` are a content read's.
|
|
606
|
+
* - `locale` is **one** declared code. Neither route declares `locales`: a singleton's or a group's
|
|
607
|
+
* read composes one locale's fields and no `_metadata.localizations`, so a list or `'*'` would be
|
|
608
|
+
* typed as an answer neither gives.
|
|
609
|
+
* - No `select`, `sort` or paging: neither route declares `fields`, `sort` or a page; no `children`:
|
|
610
|
+
* neither declares it.
|
|
611
|
+
*/
|
|
612
|
+
interface DocumentReadOptions<R extends Reading, P extends readonly string[]> {
|
|
613
|
+
/** Include paths: the references to expand into the entries they point at (REL-1801). */
|
|
614
|
+
readonly include?: CheckedIncludes<R, P>;
|
|
615
|
+
/** The locale to read, over the client's locale. */
|
|
616
|
+
readonly locale?: ReadLocale<R>;
|
|
617
|
+
/** The dataset to read, by id or name, over the client's (Q9). */
|
|
618
|
+
readonly dataset?: string;
|
|
619
|
+
/** How fresh the answer must be: a feed position, or `true` for past every cache. */
|
|
620
|
+
readonly fresh?: FreshOption;
|
|
621
|
+
}
|
|
622
|
+
/**
|
|
623
|
+
* What `version()` takes on every handle (REL-2427): the version to read, and the locale whose
|
|
624
|
+
* timeline it is numbered in — versions are numbered per locale, so version 2 is different content
|
|
625
|
+
* in each one. `locale` defaults to the client's.
|
|
626
|
+
*/
|
|
627
|
+
interface VersionOptions {
|
|
628
|
+
readonly version: number;
|
|
629
|
+
readonly locale?: string;
|
|
630
|
+
}
|
|
631
|
+
/** The Field an include path starts at: `sections` for `sections.features(name)`. */
|
|
632
|
+
type IncludeRoot<X extends string> = X extends `${infer Head}.${string}` ? PathName<Head> : PathName<X>;
|
|
633
|
+
type PathName<X extends string> = X extends `${infer Name}(${string}` ? Name : X;
|
|
634
|
+
/**
|
|
635
|
+
* `E` with only the Fields `select` named, `_metadata`, and each Field an include path starts at —
|
|
636
|
+
* which the API keeps in the projection, since an expanded reference needs its own Field. `E` when
|
|
637
|
+
* nothing is selected.
|
|
638
|
+
*/
|
|
639
|
+
type SelectedFields<E, S, I extends string> = S extends readonly [] | undefined ? E : S extends readonly string[] ? Pick<E, Extract<keyof E, S[number] | IncludeRoot<I> | '_metadata'>> : E;
|
|
640
|
+
/**
|
|
641
|
+
* The fields of the other locales a list of codes or `'*'` asked for, keyed by code: a locale with
|
|
642
|
+
* no visible fields is left out, so each is optional.
|
|
643
|
+
*/
|
|
644
|
+
type Localizations<E, C extends string> = {
|
|
645
|
+
readonly [K in C]?: Partial<Omit<E, '_metadata'>>;
|
|
646
|
+
};
|
|
647
|
+
/**
|
|
648
|
+
* `E` as a read in `L` returns it. A list of codes or `'*'` adds `_metadata.localizations`, keyed by
|
|
649
|
+
* the codes it can hold; one code, or none, leaves `E` as it is.
|
|
650
|
+
*/
|
|
651
|
+
type Localized<E, L, C extends string> = L extends '*' | readonly string[] ? E extends {
|
|
652
|
+
readonly _metadata: infer M;
|
|
653
|
+
} ? Omit<E, '_metadata'> & {
|
|
654
|
+
_metadata: Omit<M, 'localizations'> & {
|
|
655
|
+
localizations: Localizations<E, L extends readonly string[] ? L[number] : C>;
|
|
656
|
+
};
|
|
657
|
+
} : E : E;
|
|
658
|
+
/** What a read of `R` returns under its include paths `P`, selection `S` and locale `L`. */
|
|
659
|
+
type ReadAs<R extends Reading, P extends readonly string[], S = undefined, L = undefined> = Localized<SelectedFields<ReadWith<R, P[number]>, S, P[number]>, L, ReadLocale<R>>;
|
|
660
|
+
/**
|
|
661
|
+
* Where a read reads and how fresh it must be, as an implementation reads them whatever the type
|
|
662
|
+
* parameters checked: the clump every read takes — an entry read, a list, a one-document read, a
|
|
663
|
+
* search, an ad-hoc query and React's keyed reads alike. One declaration, because each surface
|
|
664
|
+
* restating it had already drifted (`fresh` was `true | string` in one, `FreshOption` in another).
|
|
665
|
+
* A read that takes one code (a search, a one-document read) narrows `locale` to `string`.
|
|
666
|
+
*/
|
|
667
|
+
interface WrittenReadScope {
|
|
668
|
+
readonly locale?: string | readonly string[];
|
|
669
|
+
readonly dataset?: string;
|
|
670
|
+
readonly fresh?: FreshOption;
|
|
671
|
+
}
|
|
672
|
+
/**
|
|
673
|
+
* A `sort` as an implementation reads it, whatever keys the type checked ({@link SortOption}): a
|
|
674
|
+
* direction per key, the key order the priority.
|
|
675
|
+
*/
|
|
676
|
+
type WrittenSort = Readonly<Record<string, 'asc' | 'desc' | undefined>>;
|
|
677
|
+
|
|
678
|
+
/**
|
|
679
|
+
* The verbs every handle over the entry routes (`/v1/{type}`, `/v1/{type}/{id}`) shares — a content
|
|
680
|
+
* type's and an asset type's alike (REL-2419, REL-2409 decision Q18): `list`, `listAll`, `get`,
|
|
681
|
+
* `update` and `delete`, written once. An asset is an entry on the same routes, so a second copy of
|
|
682
|
+
* these per handle is what drifted before: the asset handle had none of the options, the not-found
|
|
683
|
+
* split or the check from the entry.
|
|
684
|
+
*
|
|
685
|
+
* What differs per handle is in the type parameters, not in a copy:
|
|
686
|
+
*
|
|
687
|
+
* - `Revised` — what an update answers;
|
|
688
|
+
* - `Placement` — what a write may place beside its data, as its route reads it: a content entry's
|
|
689
|
+
* `slug`, `parentId` and `sortOrder`; an asset's `slug` alone, since the asset branch of
|
|
690
|
+
* `PATCH /{type}/{id}` reads no other (`EntriesController.updateEntry`).
|
|
691
|
+
*/
|
|
692
|
+
declare abstract class EntryHandle<T extends BaseContent, R extends Reading, Revised, Placement extends object> {
|
|
693
|
+
protected readonly httpClient: HttpClient;
|
|
694
|
+
protected readonly typeName: string;
|
|
695
|
+
/** The client's locale, asked on every call rather than copied when this handle was taken. */
|
|
696
|
+
protected readonly clientLocale: () => string | undefined;
|
|
697
|
+
/**
|
|
698
|
+
* @param typeName - The content or asset type the routes are addressed by
|
|
699
|
+
* @param locale - The client's locale: the function `TypedRelyIOClient` hands over, asked on
|
|
700
|
+
* every call
|
|
701
|
+
*/
|
|
702
|
+
constructor(httpClient: HttpClient, typeName: string, locale?: ClientLocale);
|
|
703
|
+
/**
|
|
704
|
+
* One page of this type's entries: the API's own `{ data, page }`, as it answered (REL-2415).
|
|
705
|
+
*
|
|
706
|
+
* Page by `cursor` — the `page.nextCursor` a page handed back — to walk, or by `page` number for an
|
|
707
|
+
* archive page like `/blog/page/3`; `limit` is the page size for both. The page is the cursor
|
|
708
|
+
* dialect's `{ nextCursor, hasMore }`: a list carries no total.
|
|
709
|
+
*
|
|
710
|
+
* The rest of the options have one name per concern (REL-2417): `select` the entry's own Fields,
|
|
711
|
+
* `sort` an order whose key order is the priority, `locale` a code, a list of codes or `'*'`,
|
|
712
|
+
* `dataset` this read's dataset, `fresh` how fresh the answer must be, `children` the children to
|
|
713
|
+
* embed. See `ListReadOptions`.
|
|
714
|
+
*
|
|
715
|
+
* @example
|
|
716
|
+
* ```typescript
|
|
717
|
+
* const { data, page } = await relyio.content('post').list({
|
|
718
|
+
* where: { category: 'news' },
|
|
719
|
+
* include: ['author'],
|
|
720
|
+
* select: ['title', 'slug'],
|
|
721
|
+
* sort: { featured: 'desc', publishedAt: 'desc' },
|
|
722
|
+
* limit: 10,
|
|
723
|
+
* page: 3,
|
|
724
|
+
* })
|
|
725
|
+
* ```
|
|
726
|
+
*/
|
|
727
|
+
list<const P extends readonly string[] = [], const S extends readonly FieldKey<T>[] | undefined = undefined, const L extends LocaleOption<ReadLocale<R>> | undefined = undefined>(options?: ListOptions<T, R, P, S, L>): Promise<EntryList<ReadAs<R, P, S, L>>>;
|
|
728
|
+
/**
|
|
729
|
+
* Every entry of this type that `where` keeps, page after page, as an async iterable: the walk
|
|
730
|
+
* follows each page's cursor and stops where the API says there is no more (REL-2415).
|
|
731
|
+
* `Array.fromAsync(relyio.content('post').listAll())` makes it an array. `limit` is how many each
|
|
732
|
+
* request reads; every other option is a list's, sent with each page.
|
|
733
|
+
*
|
|
734
|
+
* Iterating throws when a page says there is more and hands back no cursor to read it by, rather
|
|
735
|
+
* than ending the walk short.
|
|
736
|
+
*/
|
|
737
|
+
listAll<const P extends readonly string[] = [], const S extends readonly FieldKey<T>[] | undefined = undefined, const L extends LocaleOption<ReadLocale<R>> | undefined = undefined>(options?: ListAllOptions<T, R, P, S, L>): AsyncIterable<ReadAs<R, P, S, L>>;
|
|
738
|
+
private walk;
|
|
739
|
+
/**
|
|
740
|
+
* One entry by its id or slug, or `null` when there is no such entry (REL-2415).
|
|
741
|
+
*
|
|
742
|
+
* `null` only for a missing **entry**: a type the schema does not declare, or a dataset with no
|
|
743
|
+
* schema deployed, throws `ResourceNotFoundError` naming which (`context.resourceType`), so an
|
|
744
|
+
* undeployed type never reads as a missing page.
|
|
745
|
+
*
|
|
746
|
+
* It takes the options of a list that are not about which entries or in what order — `include`,
|
|
747
|
+
* `locale`, `dataset`, `fresh`, `children` (REL-2417). The route declares no `fields`, so there is
|
|
748
|
+
* no `select`.
|
|
749
|
+
*
|
|
750
|
+
* @example
|
|
751
|
+
* ```typescript
|
|
752
|
+
* const post = await relyio.content('post').get('hello-world', { include: ['author'] })
|
|
753
|
+
* if (post === null) return notFound()
|
|
754
|
+
* ```
|
|
755
|
+
*/
|
|
756
|
+
get<const P extends readonly string[] = [], const L extends LocaleOption<ReadLocale<R>> | undefined = undefined>(idOrSlug: string, options?: GetOptions<R, P, L>): Promise<ReadAs<R, P, undefined, L> | null>;
|
|
757
|
+
/**
|
|
758
|
+
* Update an entry you read, checked against the version it was read at (REL-2418).
|
|
759
|
+
*
|
|
760
|
+
* The check comes from the entry's own `_metadata`, which every read carries whether or not the
|
|
761
|
+
* cache served it, and the write addresses the locale the entry was read in — so a read whose
|
|
762
|
+
* locale came from the browser's language is written back to that locale. If the entry changed
|
|
763
|
+
* since the read, the write is refused with `ResourceConflictError` naming the version you
|
|
764
|
+
* read and the version it holds now: re-read it with `fresh: true` and retry. The SDK does not
|
|
765
|
+
* retry on its own.
|
|
766
|
+
*
|
|
767
|
+
* Pass an id instead to write **unchecked**, on purpose — an import that holds only ids stays one
|
|
768
|
+
* request per row.
|
|
769
|
+
*
|
|
770
|
+
* @example
|
|
771
|
+
* ```typescript
|
|
772
|
+
* const post = await relyio.content('post').get('hello-world')
|
|
773
|
+
* if (post) await relyio.content('post').update(post, { title: 'Hello, world' })
|
|
774
|
+
* ```
|
|
775
|
+
*/
|
|
776
|
+
update(entry: ReadEntry, data: UpdateInput<T>, options?: Placement & RetryableWriteOptions): Promise<Revised>;
|
|
777
|
+
/**
|
|
778
|
+
* Update an entry by id, with no version check (partial update), on purpose: an import that holds
|
|
779
|
+
* only ids stays one request per row. `locale` defaults to the client's own.
|
|
780
|
+
*/
|
|
781
|
+
update(id: string, data: UpdateInput<T>, options?: Placement & {
|
|
782
|
+
locale?: string;
|
|
783
|
+
} & RetryableWriteOptions): Promise<Revised>;
|
|
784
|
+
/**
|
|
785
|
+
* Delete an entry you read, checked against the version it was read at, as {@link update} is:
|
|
786
|
+
* an entry changed since the read is refused with `ResourceConflictError` and stays.
|
|
787
|
+
*/
|
|
788
|
+
delete(entry: ReadEntry, options?: RetryableWriteOptions): Promise<void>;
|
|
789
|
+
/** Delete an entry by id, with no version check. */
|
|
790
|
+
delete(id: string, options?: RetryableWriteOptions): Promise<void>;
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
/** What a content entry's write places beside its data, as `PATCH /{contentType}/{id}` reads it. */
|
|
794
|
+
interface EntryPlacement {
|
|
795
|
+
slug?: string;
|
|
796
|
+
parentId?: string;
|
|
797
|
+
sortOrder?: number;
|
|
798
|
+
}
|
|
799
|
+
/**
|
|
800
|
+
* What a checked write takes beside the entry it was read as (REL-2418). No `locale` and no
|
|
801
|
+
* `ifMatch`: the entry names both, and a second source for either is how a check was dropped.
|
|
802
|
+
*/
|
|
803
|
+
interface EntryWriteOptions extends EntryPlacement, RetryableWriteOptions {
|
|
804
|
+
}
|
|
805
|
+
/** What `create(data, options)` takes beside the data. */
|
|
806
|
+
interface CreateEntryOptions extends EntryPlacement, RetryableWriteOptions {
|
|
807
|
+
/** The locale the entry is created in; the client's own when left out. */
|
|
808
|
+
locale?: string;
|
|
809
|
+
/** The dataset to write, by id or name, over the client's. */
|
|
810
|
+
dataset?: string;
|
|
811
|
+
}
|
|
812
|
+
/**
|
|
813
|
+
* One content type's entries: read with `list`, `listAll` and `get`, written with `create`,
|
|
814
|
+
* `update` and `delete` (each from {@link EntryHandle}), and their history and children beside.
|
|
815
|
+
*
|
|
816
|
+
* @example
|
|
817
|
+
* ```typescript
|
|
818
|
+
* const products = client.content('product')
|
|
819
|
+
*
|
|
820
|
+
* // One page, one options object
|
|
821
|
+
* const { data: active, page } = await products.list({
|
|
822
|
+
* where: { status: 'active' },
|
|
823
|
+
* locale: 'en',
|
|
824
|
+
* include: ['brand'],
|
|
825
|
+
* limit: 20,
|
|
826
|
+
* })
|
|
827
|
+
*
|
|
828
|
+
* // Every entry, page after page
|
|
829
|
+
* for await (const product of products.listAll()) console.log(product.title)
|
|
830
|
+
* ```
|
|
831
|
+
*/
|
|
832
|
+
declare class ContentClient<T extends BaseContent = BaseContent, R extends Reading = UntypedReading<T>> extends EntryHandle<T, R, RevisedContent<T>, EntryPlacement> {
|
|
833
|
+
/**
|
|
834
|
+
* Create a new content entry.
|
|
835
|
+
*
|
|
836
|
+
* `locale` defaults to the client's own, and `dataset` to the client's (`client.with({ dataset })`
|
|
837
|
+
* sets it for every call).
|
|
838
|
+
*
|
|
839
|
+
* @example
|
|
840
|
+
* ```typescript
|
|
841
|
+
* const post = await relyio.content('post').create({ title: 'Hello' }, { slug: 'hello' })
|
|
842
|
+
* ```
|
|
843
|
+
*/
|
|
844
|
+
create(data: CreateInput<T>, options?: CreateEntryOptions): Promise<T>;
|
|
845
|
+
/**
|
|
846
|
+
* Get version history for one locale of an entry.
|
|
847
|
+
*
|
|
848
|
+
* Versions are numbered per locale, so this is one locale's timeline: `locale` defaults to the
|
|
849
|
+
* client's own, else the schema default; no fallback is applied, and a locale
|
|
850
|
+
* with no history returns an empty array rather than another locale's rows. Every row names the
|
|
851
|
+
* locale it belongs to.
|
|
852
|
+
*/
|
|
853
|
+
versions(id: string, options?: {
|
|
854
|
+
limit?: number;
|
|
855
|
+
locale?: string;
|
|
856
|
+
}): Promise<Array<T & {
|
|
857
|
+
versionNumber: number;
|
|
858
|
+
locale: string;
|
|
859
|
+
}>>;
|
|
860
|
+
/**
|
|
861
|
+
* One version of an entry: `version(id, { version, locale })`.
|
|
862
|
+
*
|
|
863
|
+
* `locale` defaults to the client's own, else the schema default. Version numbers are per locale,
|
|
864
|
+
* so version 2 is different content in each one.
|
|
865
|
+
*
|
|
866
|
+
* There is no `null`: a version the locale does not have, an entry that is not there, a type the
|
|
867
|
+
* schema does not declare and a dataset with no schema all throw `ResourceNotFoundError`, its
|
|
868
|
+
* `context.resourceType` naming which (REL-2409 Q12).
|
|
869
|
+
*/
|
|
870
|
+
version(id: string, options: VersionOptions): Promise<T>;
|
|
871
|
+
/**
|
|
872
|
+
* Revert one locale of an entry you read to a previous version: `restore(entry, { version })`,
|
|
873
|
+
* checked against the version the entry carries, in the locale it was read in — as
|
|
874
|
+
* `update(entry, data)` is. An entry changed since the read is refused with
|
|
875
|
+
* `ResourceConflictError`: re-read it with `fresh: true` and retry.
|
|
876
|
+
*
|
|
877
|
+
* Only that locale's fields change — shared fields and every other locale are left untouched. The
|
|
878
|
+
* restore is validated as strictly as a publish, so it cannot publish invalid content, and it
|
|
879
|
+
* records a new version of its own rather than rewinding the counter — so the revert is auditable
|
|
880
|
+
* and itself reversible.
|
|
881
|
+
*
|
|
882
|
+
* @returns The entry, its `_metadata.leftOut` naming each value of the version the active schema
|
|
883
|
+
* no longer declares, and `_metadata.brokenLinks` the entry links it left pointing nowhere.
|
|
884
|
+
*/
|
|
885
|
+
restore(entry: ReadEntry, options: {
|
|
886
|
+
version: number;
|
|
887
|
+
} & RetryableWriteOptions): Promise<RestoredContent<T>>;
|
|
888
|
+
/**
|
|
889
|
+
* Revert one locale of an entry by id, with no version check: `restore(id, { version, locale })`.
|
|
890
|
+
*
|
|
891
|
+
* `locale` is **required**, and that is not a convention: versions are numbered per locale, so
|
|
892
|
+
* `version` alone is ambiguous, and defaulting it would silently revert the wrong translation.
|
|
893
|
+
*/
|
|
894
|
+
restore(id: string, options: {
|
|
895
|
+
version: number;
|
|
896
|
+
locale: string;
|
|
897
|
+
} & RetryableWriteOptions): Promise<RestoredContent<T>>;
|
|
898
|
+
/**
|
|
899
|
+
* The direct children of a hierarchical entry, each read as `get` reads an entry.
|
|
900
|
+
*
|
|
901
|
+
* It takes `get`'s options — `include` (checked against the type), `locale` (a code, a list or
|
|
902
|
+
* `'*'`), `dataset`, `fresh` and `children` — the terms its route declares as the entry route
|
|
903
|
+
* does, sent the same way. The route lists one level and takes no depth (REL-2195); a tree nested
|
|
904
|
+
* under each child is the `children` option.
|
|
905
|
+
*
|
|
906
|
+
* @param id - Parent entry ID
|
|
907
|
+
*/
|
|
908
|
+
children<const P extends readonly string[] = [], const L extends LocaleOption<ReadLocale<R>> | undefined = undefined>(id: string, options?: GetOptions<R, P, L>): Promise<Array<ReadAs<R, P, undefined, L>>>;
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
/**
|
|
912
|
+
* Upload/processing error taxonomy (plan 17, 00b Step 4).
|
|
913
|
+
*
|
|
914
|
+
* A **thin customer-side mirror** of the live-contracts `FileErrorCode`
|
|
915
|
+
* (correction C6 — the SDK must not import the server/Studio contracts package).
|
|
916
|
+
* Keep the members in sync with `@relyio/live-contracts`.
|
|
917
|
+
*
|
|
918
|
+
* Written once, as values, because a processing failure's code arrives as a string the service
|
|
919
|
+
* stored and has to be recognised at runtime; `FileErrorCode` is read off this list. The module is
|
|
920
|
+
* internal — `errors/index.ts` does not re-export it — so the list is not a second public name for
|
|
921
|
+
* the type.
|
|
922
|
+
*/
|
|
923
|
+
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"];
|
|
924
|
+
|
|
925
|
+
/**
|
|
926
|
+
* Upload/processing error taxonomy (plan 17, 00b Step 4): a customer-side mirror of the
|
|
927
|
+
* live-contracts `FileErrorCode`, read off the list in `./file-error-codes`.
|
|
928
|
+
*/
|
|
929
|
+
type FileErrorCode = (typeof FILE_ERROR_CODES)[number];
|
|
930
|
+
/**
|
|
931
|
+
* The code a {@link FileUploadError} carries: a {@link FileErrorCode}, or `PROCESSING_FAILED` for a
|
|
932
|
+
* processing failure this SDK cannot name — the service stored no code, as an image or file
|
|
933
|
+
* processing failure is stored today, or one this version of the SDK predates. What the service
|
|
934
|
+
* stored is in the error's `context.processingError` either way.
|
|
935
|
+
*/
|
|
936
|
+
type FileUploadErrorCode = FileErrorCode | 'PROCESSING_FAILED';
|
|
937
|
+
/**
|
|
938
|
+
* A failure during the upload protocol (presign / PUT / multipart / confirm) or
|
|
939
|
+
* surfaced by the server's processing pipeline. Carries a typed {@link FileUploadErrorCode}
|
|
940
|
+
* and a `retriable` hint so callers (and `upload()`'s bounded auto-retry) can
|
|
941
|
+
* decide whether to retry on the same entry.
|
|
942
|
+
*/
|
|
943
|
+
declare class FileUploadError extends SDKError {
|
|
944
|
+
readonly code: FileUploadErrorCode;
|
|
945
|
+
readonly retryable: boolean;
|
|
946
|
+
constructor(code: FileUploadErrorCode, message: string, options?: {
|
|
947
|
+
retriable?: boolean;
|
|
948
|
+
statusCode?: number;
|
|
949
|
+
cause?: Error;
|
|
950
|
+
context?: Record<string, unknown>;
|
|
951
|
+
});
|
|
952
|
+
getUserMessage(): string;
|
|
953
|
+
}
|
|
954
|
+
/**
|
|
955
|
+
* Client-side file validation failure (MIME/size/etc.) raised **before** any
|
|
956
|
+
* bytes leave the machine (UX only — the server presign stays authoritative).
|
|
957
|
+
*/
|
|
958
|
+
declare class FileValidationError extends BaseValidationError {
|
|
959
|
+
readonly code = "FILE_VALIDATION_ERROR";
|
|
960
|
+
constructor(message: string, violations?: ValidationViolation[], cause?: Error);
|
|
961
|
+
getUserMessage(): string;
|
|
962
|
+
}
|
|
963
|
+
/** Constraints `validateFile` checks a file against (subset of the schema upload gate). */
|
|
964
|
+
interface FileValidationConstraints {
|
|
965
|
+
/** Allowed MIME types (exact match). */
|
|
966
|
+
allowedMimeTypes?: string[];
|
|
967
|
+
/** Max size in bytes. */
|
|
968
|
+
maxSizeBytes?: number;
|
|
969
|
+
}
|
|
970
|
+
/**
|
|
971
|
+
* Validate a file against explicit constraints, returning typed violations.
|
|
972
|
+
*
|
|
973
|
+
* The customer SDK does not carry a `SchemaReader` (that lives in the Studio
|
|
974
|
+
* layer), so callers pass the constraints they want enforced client-side; the
|
|
975
|
+
* server presign remains the authority. Returns an empty array when valid.
|
|
976
|
+
*/
|
|
977
|
+
declare function validateFile(file: {
|
|
978
|
+
type?: string;
|
|
979
|
+
size?: number;
|
|
980
|
+
}, constraints?: FileValidationConstraints): ValidationViolation[];
|
|
981
|
+
|
|
982
|
+
/** Delivery visibility for a file binding (gateway-enforced, no byte relocation). */
|
|
983
|
+
type AssetVisibility = 'public' | 'protected' | 'private';
|
|
984
|
+
/** Binary lifecycle status — independent of the entry's publishing status. */
|
|
985
|
+
type FileStatus = 'pending_upload' | 'uploading' | 'processing' | 'ready' | 'failed';
|
|
986
|
+
/** Storage backend: `r2` = image + file kinds, `stream` = video kind. */
|
|
987
|
+
type FileBackend = 'r2' | 'stream';
|
|
988
|
+
/** Extracted media metadata (Phase 1 basics + Phase 2 enrichment, plans 15–19); open-ended. */
|
|
989
|
+
type AssetFileExtracted = NonNullable<AssetFileBlock['extracted']>;
|
|
990
|
+
/**
|
|
991
|
+
* Where one file binding's upload got to, as {@link AssetFileBuilder.status} reads it
|
|
992
|
+
* (`GET /v1/{assetType}/{id}/file`): the document's `FileStatus` — the binding's lifecycle and
|
|
993
|
+
* nothing of the entry's content. `processing.error` is present exactly while `status` is `failed`.
|
|
994
|
+
*/
|
|
995
|
+
type FileStatusResult = components['schemas']['FileStatus'];
|
|
996
|
+
|
|
997
|
+
/**
|
|
998
|
+
* An asset entry response: schema fields at the top level plus the reserved
|
|
999
|
+
* `file` + `urls` blocks and `_metadata`. `T` carries the inferred field shape — for a typed client
|
|
1000
|
+
* `InferAssetType`, so this is `InferAssetEntry`, the shape an expanded asset reads as too.
|
|
1001
|
+
*/
|
|
1002
|
+
type AssetEntryResponse<T extends BaseContent = BaseContent> = T & AssetServedBlocks;
|
|
1003
|
+
/** Request a single upload target for the binding. */
|
|
1004
|
+
interface PresignInput {
|
|
1005
|
+
/** Original file name (required). */
|
|
1006
|
+
fileName: string;
|
|
1007
|
+
/** MIME type the client will upload with. */
|
|
1008
|
+
contentType?: string;
|
|
1009
|
+
/** File size in bytes (early upload-gate validation). */
|
|
1010
|
+
size?: number;
|
|
1011
|
+
/** Locale to target for file-localized asset types. */
|
|
1012
|
+
locale?: string;
|
|
1013
|
+
/** Advisory client-computed SHA-256 (duplicate detection). */
|
|
1014
|
+
hash?: string;
|
|
1015
|
+
}
|
|
1016
|
+
/** Single upload target returned by `presign`. */
|
|
1017
|
+
interface PresignResult {
|
|
1018
|
+
backend: FileBackend;
|
|
1019
|
+
/** `put` = single PUT / Stream POST; `multipart` = resumable R2 session (plan 17). */
|
|
1020
|
+
mode?: 'put' | 'multipart';
|
|
1021
|
+
/** R2 presigned PUT (image/file) or Stream direct-creator URL (video). */
|
|
1022
|
+
uploadUrl?: string;
|
|
1023
|
+
method: 'PUT' | 'POST';
|
|
1024
|
+
headers?: Record<string, string>;
|
|
1025
|
+
/** Object key for the binding — present for R2 (needed to drive multipart). */
|
|
1026
|
+
objectKey?: string;
|
|
1027
|
+
storageVersion?: number;
|
|
1028
|
+
/** Multipart upload id — present only for `mode: 'multipart'`. */
|
|
1029
|
+
uploadId?: string;
|
|
1030
|
+
/** Multipart part size in bytes — present only for `mode: 'multipart'`. */
|
|
1031
|
+
partSize?: number;
|
|
1032
|
+
expiresAt: string;
|
|
1033
|
+
locale?: string;
|
|
1034
|
+
}
|
|
1035
|
+
/** Result of opening a multipart session (`file(uid).createMultipart()`). */
|
|
1036
|
+
interface MultipartCreateResult {
|
|
1037
|
+
uploadId: string;
|
|
1038
|
+
objectKey: string;
|
|
1039
|
+
partSize: number;
|
|
1040
|
+
storageVersion: number;
|
|
1041
|
+
locale?: string;
|
|
1042
|
+
}
|
|
1043
|
+
/** A single uploaded part (`{ PartNumber, ETag }` ordering for `complete`). */
|
|
1044
|
+
interface MultipartPart {
|
|
1045
|
+
partNumber: number;
|
|
1046
|
+
etag: string;
|
|
1047
|
+
size?: number;
|
|
1048
|
+
}
|
|
1049
|
+
/** Target a multipart session in subsequent ops. */
|
|
1050
|
+
interface MultipartTarget {
|
|
1051
|
+
uploadId: string;
|
|
1052
|
+
objectKey: string;
|
|
1053
|
+
locale?: string;
|
|
1054
|
+
}
|
|
1055
|
+
/** Upload progress tick handed to `.onProgress()`. */
|
|
1056
|
+
interface UploadProgress {
|
|
1057
|
+
/** `upload` for client → storage bytes. */
|
|
1058
|
+
phase: 'upload';
|
|
1059
|
+
/** Bytes transferred so far. */
|
|
1060
|
+
loaded: number;
|
|
1061
|
+
/** Total bytes (when known). */
|
|
1062
|
+
total: number;
|
|
1063
|
+
/** 0–100 integer. */
|
|
1064
|
+
progress: number;
|
|
1065
|
+
}
|
|
1066
|
+
/** Acknowledge a completed client upload. */
|
|
1067
|
+
interface ConfirmInput {
|
|
1068
|
+
locale?: string;
|
|
1069
|
+
hash?: string;
|
|
1070
|
+
}
|
|
1071
|
+
/** Result of `confirm`. */
|
|
1072
|
+
interface ConfirmResult {
|
|
1073
|
+
accepted: boolean;
|
|
1074
|
+
}
|
|
1075
|
+
/** Resolve a single gateway delivery URL. */
|
|
1076
|
+
interface ResolveUrlInput {
|
|
1077
|
+
locale?: string;
|
|
1078
|
+
/** Named image variant id. */
|
|
1079
|
+
variantId?: string;
|
|
1080
|
+
/** Video delivery target. */
|
|
1081
|
+
purpose?: 'playback' | 'poster' | 'download';
|
|
1082
|
+
}
|
|
1083
|
+
/** Result of `url`. `expiresAt` is omitted for `public` bindings. */
|
|
1084
|
+
interface ResolveUrlResult {
|
|
1085
|
+
url: string;
|
|
1086
|
+
expiresAt?: string;
|
|
1087
|
+
}
|
|
1088
|
+
/** Options when creating an asset entry. */
|
|
1089
|
+
interface CreateAssetOptions {
|
|
1090
|
+
slug?: string;
|
|
1091
|
+
locale?: string;
|
|
1092
|
+
/** Original file name (also settable at presign). */
|
|
1093
|
+
fileName?: string;
|
|
1094
|
+
/** Initial delivery visibility (default `public`). */
|
|
1095
|
+
visibility?: AssetVisibility;
|
|
1096
|
+
/** Auto-publish the entry when the file becomes ready (default `true`). */
|
|
1097
|
+
publish?: boolean;
|
|
1098
|
+
}
|
|
1099
|
+
/**
|
|
1100
|
+
* What an asset's write places beside its data: its `slug` alone, the one the asset branch of
|
|
1101
|
+
* `PATCH /{assetType}/{id}` reads (`EntriesController.updateEntry`) — a `parentId` or `sortOrder`
|
|
1102
|
+
* would be dropped there, so neither is offered.
|
|
1103
|
+
*/
|
|
1104
|
+
interface AssetPlacement {
|
|
1105
|
+
slug?: string;
|
|
1106
|
+
}
|
|
1107
|
+
/** Options for {@link AssetClient.update} by id: its placement, its locale and a retry key. */
|
|
1108
|
+
type UpdateAssetOptions = AssetPlacement & {
|
|
1109
|
+
locale?: string;
|
|
1110
|
+
} & RetryableWriteOptions;
|
|
1111
|
+
/** Options for {@link AssetFileBuilder.waitUntilReady}. */
|
|
1112
|
+
interface WaitUntilReadyOptions {
|
|
1113
|
+
/** Locale of the binding to watch. */
|
|
1114
|
+
locale?: string;
|
|
1115
|
+
/**
|
|
1116
|
+
* Total time to wait before giving up (ms), from 0 to 2147483647. Default 60000. `0` reads the
|
|
1117
|
+
* file once and rejects with a `TimeoutError` if it is not ready.
|
|
1118
|
+
*/
|
|
1119
|
+
timeoutMs?: number;
|
|
1120
|
+
/** Delay between polls (ms), from 1 to 2147483647. Default 1500. */
|
|
1121
|
+
pollIntervalMs?: number;
|
|
1122
|
+
}
|
|
1123
|
+
/**
|
|
1124
|
+
* Operations on a single asset entry's file binding, backed by the
|
|
1125
|
+
* `/{assetType}/{entryUid}/file/*` routes (plan 06 §2).
|
|
1126
|
+
*/
|
|
1127
|
+
declare class AssetFileBuilder<T extends BaseContent = BaseContent> {
|
|
1128
|
+
private readonly httpClient;
|
|
1129
|
+
private readonly assetType;
|
|
1130
|
+
private readonly entryUid;
|
|
1131
|
+
/** The client's locale, asked on every call rather than copied when this builder was taken. */
|
|
1132
|
+
private readonly clientLocale;
|
|
1133
|
+
/**
|
|
1134
|
+
* @param locale - The client's locale, sent when a call names none: the function
|
|
1135
|
+
* `TypedRelyIOClient` hands over, or a locale fixed for this builder
|
|
1136
|
+
*/
|
|
1137
|
+
constructor(httpClient: HttpClient, assetType: string, entryUid: string, locale?: ClientLocale);
|
|
1138
|
+
/**
|
|
1139
|
+
* The path parameters every `/file/*` route on this builder takes.
|
|
1140
|
+
*
|
|
1141
|
+
* The path *template* is the document's; only these two values are the
|
|
1142
|
+
* builder's, so a route that gains or loses a segment is a compile error here
|
|
1143
|
+
* rather than a hand-built string that silently misses.
|
|
1144
|
+
*/
|
|
1145
|
+
private get target();
|
|
1146
|
+
private localeOf;
|
|
1147
|
+
/**
|
|
1148
|
+
* The `locale` body field of a multipart call: the session's, else the client's. The API finds
|
|
1149
|
+
* the binding by it, so a session a `with({ locale })` client opened must name the same locale on
|
|
1150
|
+
* every later call or its `objectKey` is refused as another binding's.
|
|
1151
|
+
*/
|
|
1152
|
+
private localeField;
|
|
1153
|
+
/**
|
|
1154
|
+
* Issue a single upload target — an R2 presigned PUT (image/file) or a
|
|
1155
|
+
* Cloudflare Stream direct-creator URL (video). Resumable multipart is plan 17.
|
|
1156
|
+
*/
|
|
1157
|
+
presign(input: PresignInput): Promise<PresignResult>;
|
|
1158
|
+
/**
|
|
1159
|
+
* Acknowledge a completed client upload (kicks off the fast-path intake for
|
|
1160
|
+
* R2; a no-op for Stream, whose readiness is webhook-driven).
|
|
1161
|
+
*/
|
|
1162
|
+
confirm(input?: ConfirmInput): Promise<ConfirmResult>;
|
|
1163
|
+
/** Open an R2 multipart session for a large file (standalone of `presign`). */
|
|
1164
|
+
createMultipart(input: PresignInput): Promise<MultipartCreateResult>;
|
|
1165
|
+
/** Presign a single part PUT (1-based `partNumber`). */
|
|
1166
|
+
signPart(target: MultipartTarget & {
|
|
1167
|
+
partNumber: number;
|
|
1168
|
+
}): Promise<{
|
|
1169
|
+
url: string;
|
|
1170
|
+
}>;
|
|
1171
|
+
/** List parts already uploaded (cross-session resume). */
|
|
1172
|
+
listParts(target: MultipartTarget): Promise<MultipartPart[]>;
|
|
1173
|
+
/** Finalize a multipart session. */
|
|
1174
|
+
completeMultipart(target: MultipartTarget & {
|
|
1175
|
+
parts: MultipartPart[];
|
|
1176
|
+
}): Promise<{
|
|
1177
|
+
ok: boolean;
|
|
1178
|
+
etag?: string;
|
|
1179
|
+
}>;
|
|
1180
|
+
/** Abort a multipart session, freeing staged parts. */
|
|
1181
|
+
abortMultipart(target: MultipartTarget): Promise<{
|
|
1182
|
+
aborted: boolean;
|
|
1183
|
+
}>;
|
|
1184
|
+
/**
|
|
1185
|
+
* Resolve a single gateway delivery URL for the requested locale/variant/purpose.
|
|
1186
|
+
* Throws if the binding is not `ready`.
|
|
1187
|
+
*/
|
|
1188
|
+
url(input?: ResolveUrlInput): Promise<ResolveUrlResult>;
|
|
1189
|
+
/**
|
|
1190
|
+
* Read where this file's upload got to — `status`, and the processing stage, progress and error
|
|
1191
|
+
* the pipeline recorded — whatever the asset entry is published in.
|
|
1192
|
+
*
|
|
1193
|
+
* An asset is a draft until its file is ready, and stays one when its file fails, so a key that
|
|
1194
|
+
* reads published content finds no entry for an upload in flight or one that failed. This read
|
|
1195
|
+
* answers for the binding itself. It takes the capability the upload calls take, so a read-only
|
|
1196
|
+
* key is refused with `ReadOnlyKeyError`, and it never returns the entry's fields.
|
|
1197
|
+
*/
|
|
1198
|
+
status(options?: {
|
|
1199
|
+
locale?: string;
|
|
1200
|
+
}): Promise<FileStatusResult>;
|
|
1201
|
+
/**
|
|
1202
|
+
* Update the binding's delivery visibility (token-based; no byte relocation —
|
|
1203
|
+
* a downgrade purges the gateway cache). Returns the refreshed asset entry.
|
|
1204
|
+
*/
|
|
1205
|
+
updateVisibility(visibility: AssetVisibility, options?: {
|
|
1206
|
+
locale?: string;
|
|
1207
|
+
}): Promise<AssetEntryResponse<T>>;
|
|
1208
|
+
/**
|
|
1209
|
+
* Set the binding's normalized (0..1, origin top-left) smart-crop focal point
|
|
1210
|
+
* (plan 18) so `cover`/`crop` variants frame the subject. Per locale for
|
|
1211
|
+
* file-localized asset types; token-based (no byte change). Returns the
|
|
1212
|
+
* refreshed asset entry.
|
|
1213
|
+
*/
|
|
1214
|
+
setFocalPoint(focalPoint: {
|
|
1215
|
+
x: number;
|
|
1216
|
+
y: number;
|
|
1217
|
+
}, options?: {
|
|
1218
|
+
locale?: string;
|
|
1219
|
+
}): Promise<AssetEntryResponse<T>>;
|
|
1220
|
+
/**
|
|
1221
|
+
* Remove the file binding, clean up the underlying bytes, and purge the
|
|
1222
|
+
* gateway cache. The asset entry itself is preserved.
|
|
1223
|
+
*/
|
|
1224
|
+
delete(options?: {
|
|
1225
|
+
locale?: string;
|
|
1226
|
+
}): Promise<void>;
|
|
1227
|
+
/**
|
|
1228
|
+
* Poll the asset entry until its file binding reaches `ready`, and resolve with the ready entry.
|
|
1229
|
+
*
|
|
1230
|
+
* Each poll reads the entry through the key's lens. When that finds nothing — a key that reads
|
|
1231
|
+
* published content, and an asset that is a draft until its file is ready and stays one when it
|
|
1232
|
+
* fails — the poll reads the binding itself ({@link status}), so a failed upload rejects as one
|
|
1233
|
+
* rather than running out the limit (REL-1481).
|
|
1234
|
+
*
|
|
1235
|
+
* @throws {FileUploadError} When processing failed. `code` is the processing code the service
|
|
1236
|
+
* stored — `UNSUPPORTED_CODEC`, `CORRUPT_FILE` and so on — or `PROCESSING_FAILED` where it
|
|
1237
|
+
* stored none this SDK knows; `context.processingError` holds what it stored, and `retryable`
|
|
1238
|
+
* is its `retriable` hint.
|
|
1239
|
+
* @throws {TimeoutError} When `timeoutMs` passes first. The file may still become ready; the
|
|
1240
|
+
* error's `context.timeout` is the limit and `context.fileStatus` the status last seen. A file
|
|
1241
|
+
* that is `ready` in an asset the key's lens never shows — created with `publish: false`, or
|
|
1242
|
+
* awaiting a workflow — ends here too, and the message says so.
|
|
1243
|
+
* @throws {ReadOnlyKeyError} When the key's lens does not show the asset and the key may not read
|
|
1244
|
+
* its binding either: nothing it can read would ever report the file.
|
|
1245
|
+
*/
|
|
1246
|
+
/** The asset entry through the key's lens, or `null` where that lens finds none. */
|
|
1247
|
+
private readEntry;
|
|
1248
|
+
waitUntilReady(options?: WaitUntilReadyOptions): Promise<AssetEntryResponse<T>>;
|
|
1249
|
+
}
|
|
1250
|
+
/**
|
|
1251
|
+
* High-level one-shot upload: create draft entry → presign → PUT bytes →
|
|
1252
|
+
* confirm → (optionally) wait for `ready`. Handles the common image/file flow
|
|
1253
|
+
* (single R2 PUT). For advanced/resumable flows (large files, video tus,
|
|
1254
|
+
* progress) use the lower-level `file(uid)` builder — full upload resilience is
|
|
1255
|
+
* plan 17.
|
|
1256
|
+
*/
|
|
1257
|
+
declare class AssetUploadBuilder<T extends BaseContent = BaseContent> {
|
|
1258
|
+
private readonly httpClient;
|
|
1259
|
+
private readonly assetType;
|
|
1260
|
+
private readonly file;
|
|
1261
|
+
private _fileName?;
|
|
1262
|
+
private _visibility?;
|
|
1263
|
+
private _locale?;
|
|
1264
|
+
private _publish?;
|
|
1265
|
+
private _content;
|
|
1266
|
+
private _slug?;
|
|
1267
|
+
private _waitForReady;
|
|
1268
|
+
private _waitOptions;
|
|
1269
|
+
private _onProgress?;
|
|
1270
|
+
private _validate?;
|
|
1271
|
+
private _maxPartRetries;
|
|
1272
|
+
/** The client's locale, read once when the upload starts. */
|
|
1273
|
+
private readonly clientLocale;
|
|
1274
|
+
/**
|
|
1275
|
+
* @param locale - The client's locale, the upload's when `.locale()` names none: the function
|
|
1276
|
+
* `TypedRelyIOClient` hands over, or a locale fixed for this upload
|
|
1277
|
+
*/
|
|
1278
|
+
constructor(httpClient: HttpClient, assetType: string, file: Blob, locale?: ClientLocale);
|
|
1279
|
+
/** Subscribe to client → storage upload progress (single PUT + multipart). */
|
|
1280
|
+
onProgress(callback: (_progress: UploadProgress) => void): this;
|
|
1281
|
+
/**
|
|
1282
|
+
* Validate the file client-side (UX only) against explicit constraints before
|
|
1283
|
+
* any bytes leave the machine. Rejects instantly with a {@link FileValidationError}.
|
|
1284
|
+
*/
|
|
1285
|
+
validate(constraints: FileValidationConstraints): this;
|
|
1286
|
+
/** Bounded per-part retry attempts for multipart (default 3). */
|
|
1287
|
+
maxPartRetries(attempts: number): this;
|
|
1288
|
+
/** Set the original file name (defaults to `file.name` for `File` inputs). */
|
|
1289
|
+
filename(name: string): this;
|
|
1290
|
+
/** Set the initial delivery visibility (default `public`). */
|
|
1291
|
+
visibility(visibility: AssetVisibility): this;
|
|
1292
|
+
/** Target locale for file-localized asset types. */
|
|
1293
|
+
locale(locale: string): this;
|
|
1294
|
+
/** Suppress (or enable) auto-publish on ready. */
|
|
1295
|
+
publish(publish: boolean): this;
|
|
1296
|
+
/** Set the asset's metadata field values (e.g. `{ alt: '...' }`). */
|
|
1297
|
+
content(content: Record<string, unknown>): this;
|
|
1298
|
+
/** Set an explicit slug for the new entry. */
|
|
1299
|
+
slug(slug: string): this;
|
|
1300
|
+
/**
|
|
1301
|
+
* Wait until the file binding becomes `ready` before resolving.
|
|
1302
|
+
*
|
|
1303
|
+
* @throws {Error} When `timeoutMs` or `pollIntervalMs` is one the wait cannot honour — refused
|
|
1304
|
+
* here, before anything is uploaded, rather than after the bytes are in.
|
|
1305
|
+
*/
|
|
1306
|
+
waitForReady(options?: boolean | Omit<WaitUntilReadyOptions, 'locale'>): this;
|
|
1307
|
+
/** Execute the full upload flow and resolve with the asset entry. */
|
|
1308
|
+
execute(): Promise<AssetEntryResponse<T>>;
|
|
1309
|
+
/** Single presigned PUT (≤ 50 MB / Stream POST). */
|
|
1310
|
+
private uploadSingle;
|
|
1311
|
+
/**
|
|
1312
|
+
* Resumable R2 multipart: slice into `partSize` chunks, skip already-uploaded
|
|
1313
|
+
* parts (resume via `list-parts`), PUT each remaining part with bounded retry,
|
|
1314
|
+
* then `complete`. Aborts the session on unrecoverable failure.
|
|
1315
|
+
*/
|
|
1316
|
+
private uploadMultipart;
|
|
1317
|
+
/** PUT a single part, retrying transient failures up to `_maxPartRetries`. */
|
|
1318
|
+
private putPartWithRetry;
|
|
1319
|
+
private emitProgress;
|
|
1320
|
+
}
|
|
1321
|
+
/**
|
|
1322
|
+
* An asset read typed by the asset type's reading `R`: what the type's include paths make of its
|
|
1323
|
+
* fields, beside the `file` and `urls` blocks every asset read carries (REL-1801).
|
|
1324
|
+
*/
|
|
1325
|
+
interface AssetResponseReading<R extends Reading, T extends BaseContent = BaseContent> extends Reading {
|
|
1326
|
+
readonly read: AssetEntryResponse<Extract<ReadWith<R, this['include']>, BaseContent>>;
|
|
1327
|
+
readonly checked: CheckedInclude<R, this['path']>;
|
|
1328
|
+
/**
|
|
1329
|
+
* The asset type's own `where` — every path the API filters its fields by, never the `file` and
|
|
1330
|
+
* `urls` blocks — or, where no schema types it, the conditions `T`'s keys take (REL-2195).
|
|
1331
|
+
*/
|
|
1332
|
+
readonly where: ReadWhere<WhereClause<T>, R>;
|
|
1333
|
+
/** The locale codes its `locale` may name: the asset type's reading's. */
|
|
1334
|
+
readonly locale: ReadLocale<R>;
|
|
1335
|
+
}
|
|
1336
|
+
/**
|
|
1337
|
+
* One asset type's entries: `list`, `listAll`, `get`, `update` and `delete` as a content type's
|
|
1338
|
+
* handle has them (assets are `entryType: 'asset'` entries on the same `/{assetType}` routes), plus
|
|
1339
|
+
* `create`, the `file(uid)` sub-resource builder and an `upload()` helper.
|
|
1340
|
+
*/
|
|
1341
|
+
declare class AssetClient<T extends BaseContent = BaseContent, R extends Reading = UntypedReading<T>> extends EntryHandle<T, AssetResponseReading<R, T>, AssetEntryResponse<T>, AssetPlacement> {
|
|
1342
|
+
/**
|
|
1343
|
+
* Create a new asset entry as a draft. The binary is uploaded separately via
|
|
1344
|
+
* `file(uid)` (or use the high-level `upload()` helper), and the entry
|
|
1345
|
+
* auto-publishes when its file becomes ready (unless `publish: false`).
|
|
1346
|
+
* `locale` defaults to the client's own.
|
|
1347
|
+
*/
|
|
1348
|
+
create(data: CreateInput<T>, options?: CreateAssetOptions & RetryableWriteOptions): Promise<AssetEntryResponse<T>>;
|
|
1349
|
+
/** Get the file sub-resource builder for a specific asset entry. */
|
|
1350
|
+
file(entryUid: string): AssetFileBuilder<T>;
|
|
1351
|
+
/** Start a high-level upload (create → presign → PUT → confirm). */
|
|
1352
|
+
upload(file: Blob): AssetUploadBuilder<T>;
|
|
1353
|
+
}
|
|
1354
|
+
/**
|
|
1355
|
+
* Narrow an entry (e.g. a populated reference from `include`) to an asset entry.
|
|
1356
|
+
* Relies on the `_metadata.entryType` discriminator the API emits (plan 06).
|
|
1357
|
+
*
|
|
1358
|
+
* @example
|
|
1359
|
+
* ```typescript
|
|
1360
|
+
* const article = await relyio.content('article').get(id, { include: ['hero'] })
|
|
1361
|
+
* if (isAssetEntry(article?.hero)) {
|
|
1362
|
+
* console.log(article.hero.urls?.original)
|
|
1363
|
+
* }
|
|
1364
|
+
* ```
|
|
1365
|
+
*/
|
|
1366
|
+
declare function isAssetEntry<T extends BaseContent = BaseContent>(entry: unknown): entry is AssetEntryResponse<T>;
|
|
1367
|
+
/**
|
|
1368
|
+
* A reference field populated (via `include`) with a full asset entry. Identical
|
|
1369
|
+
* to {@link AssetEntryResponse}; named for readability at reference call sites.
|
|
1370
|
+
*/
|
|
1371
|
+
type PopulatedAssetReference<T extends BaseContent = BaseContent> = AssetEntryResponse<T>;
|
|
1372
|
+
|
|
1373
|
+
/**
|
|
1374
|
+
* What a one-document write sends: the data, the locale it writes (absent: the API's default) and
|
|
1375
|
+
* the `If-Match` check of a read, when it is one.
|
|
1376
|
+
*/
|
|
1377
|
+
interface DocumentWrite<T> {
|
|
1378
|
+
readonly content: DocumentFields<T>;
|
|
1379
|
+
readonly locale: string | undefined;
|
|
1380
|
+
readonly ifMatch: string | undefined;
|
|
1381
|
+
}
|
|
1382
|
+
/**
|
|
1383
|
+
* A document's fields as a write sends them: any of them, and no `_metadata` — the read that
|
|
1384
|
+
* carries one is the checked form's first argument, never the data.
|
|
1385
|
+
*/
|
|
1386
|
+
type DocumentFields<T> = Partial<Omit<T, '_metadata'>> & {
|
|
1387
|
+
readonly _metadata?: never;
|
|
1388
|
+
};
|
|
1389
|
+
/** What an unchecked one-document write takes beside its data: the locale it writes, and its key. */
|
|
1390
|
+
interface DocumentWriteOptions extends RetryableWriteOptions {
|
|
1391
|
+
/** The locale to write; the client's own when left out. */
|
|
1392
|
+
readonly locale?: string;
|
|
1393
|
+
}
|
|
1394
|
+
/** What `versions(options)` takes on a one-document handle. */
|
|
1395
|
+
interface DocumentVersionsOptions {
|
|
1396
|
+
/** How many versions to list, newest first; 10 when left out. */
|
|
1397
|
+
readonly limit?: number;
|
|
1398
|
+
/** The locale whose timeline it is; the client's own, then the schema default, when left out. */
|
|
1399
|
+
readonly locale?: string;
|
|
1400
|
+
}
|
|
1401
|
+
/** One row of a one-document handle's history: the document as it was, in the locale it names. */
|
|
1402
|
+
type DocumentVersion<T> = T & {
|
|
1403
|
+
versionNumber: number;
|
|
1404
|
+
locale: string;
|
|
1405
|
+
};
|
|
1406
|
+
/**
|
|
1407
|
+
* The verbs a singleton's and a settings group's handle share (REL-2419, REL-2409 decision Q18):
|
|
1408
|
+
* `update`, `versions` and `version`, written once, as {@link EntryHandle} writes the entry routes'
|
|
1409
|
+
* once for a content type and an asset type. The two were near-clones that had already drifted —
|
|
1410
|
+
* in which locale they treated as none, and in what `version()` answered for a missing document.
|
|
1411
|
+
*
|
|
1412
|
+
* What differs per handle is its route, so each names its own four requests; every rule about
|
|
1413
|
+
* them — which locale a call sends, which form of a write is checked, what a missing version is —
|
|
1414
|
+
* is here.
|
|
1415
|
+
*
|
|
1416
|
+
* Every call is in the locale it names, and otherwise in the client's own as it is when the call is
|
|
1417
|
+
* made; a write of a document you read writes the locale it was read in.
|
|
1418
|
+
*/
|
|
1419
|
+
declare abstract class DocumentHandle<T> {
|
|
1420
|
+
protected readonly httpClient: HttpClient;
|
|
1421
|
+
protected readonly name: string;
|
|
1422
|
+
/** The client's locale, asked on every call rather than copied when this handle was taken. */
|
|
1423
|
+
protected readonly clientLocale: () => string | undefined;
|
|
1424
|
+
/**
|
|
1425
|
+
* @param name - The singleton key or settings group id the routes are addressed by
|
|
1426
|
+
* @param locale - The client's locale: the function `TypedRelyIOClient` hands over, asked on
|
|
1427
|
+
* every call
|
|
1428
|
+
*/
|
|
1429
|
+
constructor(httpClient: HttpClient, name: string, locale?: ClientLocale);
|
|
1430
|
+
/**
|
|
1431
|
+
* Write the document you read, checked against the version it was read at (REL-2419), in the
|
|
1432
|
+
* locale it was read in — as a content entry's `update(entry, data)` is. If it changed since the
|
|
1433
|
+
* read, the write is refused with `ResourceConflictError` naming both versions: re-read it with
|
|
1434
|
+
* `fresh: true` and retry.
|
|
1435
|
+
*/
|
|
1436
|
+
update(read: ReadEntry, data: DocumentFields<T>, options?: RetryableWriteOptions): Promise<T>;
|
|
1437
|
+
/**
|
|
1438
|
+
* Write the document with no version check, on purpose, in `options.locale` or the client's own —
|
|
1439
|
+
* creating it, when nobody has yet.
|
|
1440
|
+
*/
|
|
1441
|
+
update(data: DocumentFields<T>, options?: DocumentWriteOptions): Promise<T>;
|
|
1442
|
+
/**
|
|
1443
|
+
* Version history for one locale of the document.
|
|
1444
|
+
*
|
|
1445
|
+
* Versions are numbered per locale, so this is one locale's timeline: `locale` defaults to the
|
|
1446
|
+
* client's own, then the schema default; no fallback is applied, and a locale with no history
|
|
1447
|
+
* returns an empty array rather than another locale's rows. Every row names the locale it belongs
|
|
1448
|
+
* to.
|
|
1449
|
+
*/
|
|
1450
|
+
versions(options?: DocumentVersionsOptions): Promise<Array<DocumentVersion<T>>>;
|
|
1451
|
+
/**
|
|
1452
|
+
* One version of the document: `version({ version, locale })`. `locale` defaults to the client's
|
|
1453
|
+
* own, then the schema default; version numbers are per locale, so version 2 is different content
|
|
1454
|
+
* in each one.
|
|
1455
|
+
*
|
|
1456
|
+
* There is no `null`: a version the locale does not have, a document nobody has written, one the
|
|
1457
|
+
* schema does not declare and a dataset with no schema all throw `ResourceNotFoundError`, its
|
|
1458
|
+
* `context.resourceType` naming which (REL-2409 Q12).
|
|
1459
|
+
*/
|
|
1460
|
+
version(options: VersionOptions): Promise<T>;
|
|
1461
|
+
/** `PATCH` the document's route with this write. */
|
|
1462
|
+
protected abstract patch(write: DocumentWrite<T>, options?: RetryableWriteOptions): Promise<T>;
|
|
1463
|
+
/** `GET` the document's version history route. */
|
|
1464
|
+
protected abstract readVersions(query: {
|
|
1465
|
+
limit: number;
|
|
1466
|
+
locale?: string;
|
|
1467
|
+
}): Promise<{
|
|
1468
|
+
versions: Array<DocumentVersion<T>>;
|
|
1469
|
+
}>;
|
|
1470
|
+
/** `GET` one version of the document. */
|
|
1471
|
+
protected abstract readVersion(versionNumber: string, query: {
|
|
1472
|
+
locale?: string;
|
|
1473
|
+
}): Promise<T>;
|
|
1474
|
+
/** The locale a write or a history read sends: the call's, else the client's. */
|
|
1475
|
+
protected localeOf(named: string | undefined): string | undefined;
|
|
1476
|
+
/** The write's body: its data, and the locale it names when it names one. */
|
|
1477
|
+
protected static bodyOf<T>(write: DocumentWrite<T>): {
|
|
1478
|
+
content: DocumentFields<T>;
|
|
1479
|
+
locale?: string;
|
|
1480
|
+
};
|
|
1481
|
+
/** The two headers a write sends: its check, when it has one, and its `Idempotency-Key`. */
|
|
1482
|
+
protected static headersOf(write: Pick<DocumentWrite<unknown>, 'ifMatch'>, options?: RetryableWriteOptions): Record<string, string> | undefined;
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
/**
|
|
1486
|
+
* One singleton: read with `get(options)`, written with `update`, and its history beside — the
|
|
1487
|
+
* verbs a content type's handle has, for one document (REL-2419, REL-2409 Q18). `update`,
|
|
1488
|
+
* `versions` and `version` are the ones every one-document handle shares ({@link DocumentHandle}).
|
|
1489
|
+
*
|
|
1490
|
+
* Every call is in the locale it names — the argument (`get({ locale })`, `versions({ locale })`,
|
|
1491
|
+
* `update(data, { locale })`) — and otherwise in the client's own (`client.with({ locale })`,
|
|
1492
|
+
* `updateConfig({ locale })`) as it is when the call is made, as `content()`'s are: reads, the write,
|
|
1493
|
+
* and version history alike. An update of a singleton you read writes the locale it was read in.
|
|
1494
|
+
*
|
|
1495
|
+
* @example
|
|
1496
|
+
* ```typescript
|
|
1497
|
+
* const homepage = await client.singleton('homepage').get({
|
|
1498
|
+
* include: ['featuredProducts', 'heroImage'],
|
|
1499
|
+
* locale: 'en',
|
|
1500
|
+
* })
|
|
1501
|
+
* if (homepage) await client.singleton('homepage').update(homepage, { title: 'Welcome' })
|
|
1502
|
+
* ```
|
|
1503
|
+
*/
|
|
1504
|
+
declare class SingletonClient<T = BaseSingleton, R extends Reading = UntypedReading<T>> extends DocumentHandle<T> {
|
|
1505
|
+
/**
|
|
1506
|
+
* The singleton, or `null` when nobody has written it yet (REL-2419, REL-2409 Q18).
|
|
1507
|
+
*
|
|
1508
|
+
* `null` only for a singleton with no content: one the schema does not declare, or a dataset with
|
|
1509
|
+
* no schema deployed, throws `ResourceNotFoundError` naming which (`context.resourceType`
|
|
1510
|
+
* `'singleton'` or `'schema'`), as a content type's `get` does.
|
|
1511
|
+
*
|
|
1512
|
+
* It takes a content read's options where the route declares them — `include`, `locale` (one
|
|
1513
|
+
* code), `dataset` and `fresh` (`DocumentReadOptions`).
|
|
1514
|
+
*
|
|
1515
|
+
* @example
|
|
1516
|
+
* ```typescript
|
|
1517
|
+
* const home = await relyio.singleton('homepage').get({ include: ['hero'], locale: 'fr-FR' })
|
|
1518
|
+
* if (home) await relyio.singleton('homepage').update(home, { headline: 'Bonjour' })
|
|
1519
|
+
* ```
|
|
1520
|
+
*/
|
|
1521
|
+
get<const P extends readonly string[] = []>(options?: DocumentReadOptions<R, P>): Promise<ReadWith<R, P[number]> | null>;
|
|
1522
|
+
/** The API publishes on PATCH: there is no separate publish route. */
|
|
1523
|
+
protected patch(write: DocumentWrite<T>, options?: RetryableWriteOptions): Promise<T>;
|
|
1524
|
+
protected readVersions(query: {
|
|
1525
|
+
limit: number;
|
|
1526
|
+
locale?: string;
|
|
1527
|
+
}): Promise<{
|
|
1528
|
+
versions: Array<DocumentVersion<T>>;
|
|
1529
|
+
}>;
|
|
1530
|
+
protected readVersion(versionNumber: string, query: {
|
|
1531
|
+
locale?: string;
|
|
1532
|
+
}): Promise<T>;
|
|
1533
|
+
}
|
|
1534
|
+
|
|
1535
|
+
/**
|
|
1536
|
+
* One settings group: read with `get(options)`, written with `update` and `reset`, and its history
|
|
1537
|
+
* beside — the verbs a content type's handle has, for one document (REL-2419, REL-2409 Q18).
|
|
1538
|
+
* `update`, `versions` and `version` are the ones every one-document handle shares
|
|
1539
|
+
* ({@link DocumentHandle}).
|
|
1540
|
+
*
|
|
1541
|
+
* Every call is in the locale it names — the argument (`get({ locale })`, `versions({ locale })`,
|
|
1542
|
+
* `update(data, { locale })`, `reset({ locale })`) — and otherwise in the client's own
|
|
1543
|
+
* (`client.with({ locale })`, `updateConfig({ locale })`) as it is when the call is made, as
|
|
1544
|
+
* `content()`'s are: reads, writes and version history alike. An update or reset of a group you
|
|
1545
|
+
* read writes the locale it was read in.
|
|
1546
|
+
*
|
|
1547
|
+
* The fields of a write travel as `content` beside an optional `locale`, the body the route
|
|
1548
|
+
* validates (`UpdateSettingsDto`); any other top-level key is refused `REQUEST_INVALID`.
|
|
1549
|
+
*
|
|
1550
|
+
* @example
|
|
1551
|
+
* ```typescript
|
|
1552
|
+
* const emailConfig = await client.settings('email-config').get({
|
|
1553
|
+
* include: ['defaultTemplate'],
|
|
1554
|
+
* locale: 'en',
|
|
1555
|
+
* })
|
|
1556
|
+
* await client.settings('email-config').update(emailConfig, { fromName: 'RelyIO' })
|
|
1557
|
+
* ```
|
|
1558
|
+
*/
|
|
1559
|
+
declare class SettingsClient<T = BaseSettingsGroup, R extends Reading = UntypedReading<T>> extends DocumentHandle<T> {
|
|
1560
|
+
/**
|
|
1561
|
+
* The settings group (REL-2419, REL-2409 Q18).
|
|
1562
|
+
*
|
|
1563
|
+
* Never `null`: the API answers a declared group that nobody has written yet with its declared
|
|
1564
|
+
* defaults (it initializes the group on that first read), so there is no missing group to report.
|
|
1565
|
+
* A group the schema does not declare, or a dataset with no schema deployed, throws
|
|
1566
|
+
* `ResourceNotFoundError` naming which (`context.resourceType` `'settingsGroup'` or `'schema'`) —
|
|
1567
|
+
* the not-found rule every handle's `get` follows.
|
|
1568
|
+
*
|
|
1569
|
+
* It takes a content read's options where the route declares them — `include`, `locale` (one
|
|
1570
|
+
* code), `dataset` and `fresh` (`DocumentReadOptions`).
|
|
1571
|
+
*
|
|
1572
|
+
* @example
|
|
1573
|
+
* ```typescript
|
|
1574
|
+
* const site = await relyio.settings('site').get({ include: ['logo'] })
|
|
1575
|
+
* await relyio.settings('site').update(site, { title: 'New title' })
|
|
1576
|
+
* ```
|
|
1577
|
+
*/
|
|
1578
|
+
get<const P extends readonly string[] = []>(options?: DocumentReadOptions<R, P>): Promise<ReadWith<R, P[number]>>;
|
|
1579
|
+
/**
|
|
1580
|
+
* Reset the group you read to its default values, checked against the version it was read at,
|
|
1581
|
+
* in the locale it was read in (REL-2419) — refused with `ResourceConflictError` if it changed
|
|
1582
|
+
* since the read.
|
|
1583
|
+
*/
|
|
1584
|
+
reset(read: ReadEntry, options?: RetryableWriteOptions): Promise<T>;
|
|
1585
|
+
/**
|
|
1586
|
+
* Reset the group to its default values with no version check, in `options.locale` or the
|
|
1587
|
+
* client's own. The route reads its locale from `?locale=` and takes no body, so that is where it
|
|
1588
|
+
* is sent.
|
|
1589
|
+
*/
|
|
1590
|
+
reset(options?: DocumentWriteOptions): Promise<T>;
|
|
1591
|
+
/** The group's default values, as the schema declares them. */
|
|
1592
|
+
getDefaults(): Promise<T>;
|
|
1593
|
+
protected patch(write: DocumentWrite<T>, options?: RetryableWriteOptions): Promise<T>;
|
|
1594
|
+
protected readVersions(query: {
|
|
1595
|
+
limit: number;
|
|
1596
|
+
locale?: string;
|
|
1597
|
+
}): Promise<{
|
|
1598
|
+
versions: Array<DocumentVersion<T>>;
|
|
1599
|
+
}>;
|
|
1600
|
+
protected readVersion(versionNumber: string, query: {
|
|
1601
|
+
locale?: string;
|
|
1602
|
+
}): Promise<T>;
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1605
|
+
/**
|
|
1606
|
+
* Any schema the client can be typed by. `any` is deliberate: the client accepts whatever content
|
|
1607
|
+
* types, singletons, queries and settings groups a customer declares.
|
|
1608
|
+
*
|
|
1609
|
+
* This is the constraint `TypedRelyIOClient`'s `TSchema` carries, so `RegisteredSchema` is inferred
|
|
1610
|
+
* against the same one — `createClient` returns `TypedRelyIOClient<RegisteredSchema>`, and a schema
|
|
1611
|
+
* inferred against any wider alias would not be provably inside the client's bound.
|
|
1612
|
+
*/
|
|
1613
|
+
type AnySchemaDefinition = SchemaDefinition<any, any, any, any, any>;
|
|
1614
|
+
/**
|
|
1615
|
+
* The seam `relyio generate-types` fills (`INV-SS-TYPES-INFERRED`).
|
|
1616
|
+
*
|
|
1617
|
+
* Empty here on purpose. The generated `schema.ts` augments it —
|
|
1618
|
+
*
|
|
1619
|
+
* ```ts
|
|
1620
|
+
* declare module '@relyio/sdk' {
|
|
1621
|
+
* interface Register {
|
|
1622
|
+
* schema: typeof schema
|
|
1623
|
+
* }
|
|
1624
|
+
* }
|
|
1625
|
+
* ```
|
|
1626
|
+
*
|
|
1627
|
+
* — and from then on `createClient({ apiKey })`, with no generic and no `schema:`, is typed by that
|
|
1628
|
+
* schema. The pattern is TanStack Router's. It is type-level only: nothing at runtime reads it.
|
|
1629
|
+
*/
|
|
1630
|
+
interface Register {
|
|
1631
|
+
}
|
|
1632
|
+
/**
|
|
1633
|
+
* The schema the project registered through `Register`, or `undefined` when nothing did — which is
|
|
1634
|
+
* the untyped client, exactly as before the generator ran.
|
|
1635
|
+
*/
|
|
1636
|
+
type RegisteredSchema = Register extends {
|
|
1637
|
+
schema: infer S extends AnySchemaDefinition;
|
|
1638
|
+
} ? S : undefined;
|
|
1639
|
+
|
|
1640
|
+
/**
|
|
1641
|
+
* What the search family's routes answer, read from the committed document, and what a search over
|
|
1642
|
+
* a client's content types returns (`search-options.ts` takes the options).
|
|
1643
|
+
*/
|
|
1644
|
+
|
|
1645
|
+
/**
|
|
1646
|
+
* What a search or similar hit reports beside its fields: the committed document's
|
|
1647
|
+
* `SearchHitMetadata`, read through `http/api-schema.ts`.
|
|
1648
|
+
*
|
|
1649
|
+
* Narrower than an entry read's `EntryMetadata`, because a hit is not built by the entry read: it
|
|
1650
|
+
* carries no `entryType`, `defaultLocale`, `availableLocales`, `refs`, `firstPublishedAt` or
|
|
1651
|
+
* version history. Until REL-1413 a hit was typed as an entry and so promised all five — the
|
|
1652
|
+
* locale helpers kept an optional chain after `availableLocales` for the hits that lacked it. Read
|
|
1653
|
+
* the entry (`content(type).get(hit._metadata.id)`) for those.
|
|
1654
|
+
*/
|
|
1655
|
+
type SearchHitMetadata = components['schemas']['SearchHitMetadata'];
|
|
1656
|
+
/** What a hit carries beside the entry's fields, as the document declares it. */
|
|
1657
|
+
type SearchHitSystemFields = Pick<components['schemas']['SearchHit'], '_metadata' | '_score' | '_scoreBreakdown' | '_highlights'>;
|
|
1658
|
+
/**
|
|
1659
|
+
* One search hit as the route emits it — the document's `SearchHit`: the fields `T` declares at the
|
|
1660
|
+
* top level (at `_metadata.locale`, or only the aliases a `fields` projection names), the hit's own
|
|
1661
|
+
* `_metadata`, and the score annotations the request asked for. There is no per-item `data` wrapper.
|
|
1662
|
+
*
|
|
1663
|
+
* `T`'s `_metadata` is replaced, not kept: `T` is an entry type, and a hit is not an entry read.
|
|
1664
|
+
* Every field is optional, a required one too: a `fields` projection names only some of them, and a
|
|
1665
|
+
* hit is built from the search row, not by the read that fills a required field a draft holds empty
|
|
1666
|
+
* (REL-1795). Distributes over a union of entry types, so a search across two types keeps each
|
|
1667
|
+
* one's fields.
|
|
1668
|
+
*/
|
|
1669
|
+
type SearchResultItem<T = unknown> = T extends unknown ? Partial<Omit<T, '_metadata'>> & SearchHitSystemFields : never;
|
|
1670
|
+
/**
|
|
1671
|
+
* What `POST /v1/search` serves, **read from the committed document**.
|
|
1672
|
+
*
|
|
1673
|
+
* `{ data, page: { number, limit, total, pageCount }, meta }` — search is the page dialect
|
|
1674
|
+
* (Decision 5, amended for REL-2420): `total` counts every entry the search matched. The facets,
|
|
1675
|
+
* the strategy the engine chose and its warnings ride the declared `meta` sibling because none of
|
|
1676
|
+
* them is an item or a page fact.
|
|
1677
|
+
*
|
|
1678
|
+
* The hand-written `{ items, total?, metadata? }` this replaced named three keys the route does not
|
|
1679
|
+
* send.
|
|
1680
|
+
*/
|
|
1681
|
+
type SearchResponse<T = unknown> = Wire<'/v1/search', 'post', SearchResultItem<T>>;
|
|
1682
|
+
/**
|
|
1683
|
+
* One autocomplete suggestion — the document's `AutocompleteSuggestion`: the value, its best score,
|
|
1684
|
+
* an entry holding it (`id`), and the value with its match wrapped in `<em>` when Atlas highlighted
|
|
1685
|
+
* it. The hand-written type this replaces named `highlighted`, `entryId` and `contentType`, none of
|
|
1686
|
+
* which the route sends, and made `score` optional where it is always sent.
|
|
1687
|
+
*/
|
|
1688
|
+
type AutocompleteSuggestion = components['schemas']['AutocompleteSuggestion'];
|
|
1689
|
+
/**
|
|
1690
|
+
* What `POST /v1/search/autocomplete` serves. The suggestions are the
|
|
1691
|
+
* collection's `data`; the query it completed and the field it matched on ride
|
|
1692
|
+
* `meta`. Autocomplete is unpaginated, so its page reports `offset: 0` and a
|
|
1693
|
+
* `total` equal to what it returned — the dialect is declared rather than
|
|
1694
|
+
* omitted so one collection shape covers every list.
|
|
1695
|
+
*/
|
|
1696
|
+
type AutocompleteResponse = Wire<'/v1/search/autocomplete', 'post', AutocompleteSuggestion>;
|
|
1697
|
+
/**
|
|
1698
|
+
* One similar-content hit as the route emits it: the same `SearchHit` a search serves, built by
|
|
1699
|
+
* the same transform, with `_score` on every hit.
|
|
1700
|
+
*/
|
|
1701
|
+
type SimilarResult<T = unknown> = SearchResultItem<T>;
|
|
1702
|
+
/** What `POST /v1/search/similar` serves. */
|
|
1703
|
+
type SimilarResponse<T = unknown> = Wire<'/v1/search/similar', 'post', SimilarResult<T>>;
|
|
1704
|
+
/**
|
|
1705
|
+
* The content type names a search may be scoped to: the schema's where one types the client, any
|
|
1706
|
+
* name where none does.
|
|
1707
|
+
*/
|
|
1708
|
+
type SearchTypeName<S extends AnySchemaDefinition | undefined> = S extends AnySchemaDefinition ? SchemaContentTypeNames<S> : string;
|
|
1709
|
+
/**
|
|
1710
|
+
* What a search over the content types `K` returns: their entries, typed by the schema
|
|
1711
|
+
* (`INV-SS-TYPES-INFERRED`) — or `T` unchanged where no schema types the client.
|
|
1712
|
+
*/
|
|
1713
|
+
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;
|
|
1714
|
+
|
|
1715
|
+
/**
|
|
1716
|
+
* The search family in one options object each (REL-2420; REL-2409 decisions Q3 and Q19).
|
|
1717
|
+
*
|
|
1718
|
+
* `search(q, options)` pages by number — `page` and `limit`, as an archive page does — and answers
|
|
1719
|
+
* the API's page dialect, `{ number, limit, total, pageCount }`, so "page 2 of 3" needs no
|
|
1720
|
+
* arithmetic. It takes the read options that apply to a search, under the names every read uses:
|
|
1721
|
+
* `select`, `locale` (one code: a search composes its hits in one locale), `dataset` and `fresh`.
|
|
1722
|
+
* `autocomplete(q, options)` and `similar(id, options)` take one options object too.
|
|
1723
|
+
*
|
|
1724
|
+
* The routes declare no request body in the committed document, so each body is written here by
|
|
1725
|
+
* hand, in the route's own words: `select` is the route's `fields` alias map with each Field under
|
|
1726
|
+
* its own name, and `sort` an array in the object's key order. `dataset` and `fresh` are the same
|
|
1727
|
+
* header and query term every read sends ({@link readHeaders}).
|
|
1728
|
+
*
|
|
1729
|
+
*/
|
|
1730
|
+
|
|
1731
|
+
/**
|
|
1732
|
+
* The Fields an entry of `E` declares, by key — of each type, where `E` is a union of the types a
|
|
1733
|
+
* search spans. Any name where `E` declares none: a client no schema types.
|
|
1734
|
+
*/
|
|
1735
|
+
type SearchFieldKey<E> = [E] extends [never] ? string : E extends unknown ? [Exclude<keyof E, '_metadata'>] extends [never] ? string : Exclude<keyof E, '_metadata'> & string : never;
|
|
1736
|
+
/**
|
|
1737
|
+
* Which content types a search spans: one name, or a list of them. Left out, every content type
|
|
1738
|
+
* the key can see.
|
|
1739
|
+
*/
|
|
1740
|
+
type SearchScope<K extends string> = K | readonly [K, ...K[]];
|
|
1741
|
+
/**
|
|
1742
|
+
* How hits are ordered: a direction per key, the **key order** the priority (Q14). `_score` is
|
|
1743
|
+
* relevance itself; any other key is a Field of the types searched. Left out, by relevance.
|
|
1744
|
+
*/
|
|
1745
|
+
type SearchSortOption<E> = {
|
|
1746
|
+
readonly [K in SearchFieldKey<E> | '_score']?: 'asc' | 'desc';
|
|
1747
|
+
};
|
|
1748
|
+
/**
|
|
1749
|
+
* What a search's `where` takes over the content types `K`: the `where` `list` takes on each of them,
|
|
1750
|
+
* typed as `list` types it (REL-2427; one dialect, D11) — Field names checked, `$`-prefixed
|
|
1751
|
+
* operators, each value of its Field's type. Over several types, a `where` one of them takes. A
|
|
1752
|
+
* client no schema types takes the untyped list's `where`.
|
|
1753
|
+
*/
|
|
1754
|
+
type SearchWhere<S extends AnySchemaDefinition | undefined, K extends string> = S extends AnySchemaDefinition ? K extends SchemaContentTypeNames<S> ? NonNullable<ListReadOptions<InferContentType<S['contentTypes'][K], S>, ContentReading<S['contentTypes'][K], S>, [
|
|
1755
|
+
]>['where']> : never : NonNullable<ListReadOptions<BaseContent, UntypedReading<BaseContent>, []>['where']>;
|
|
1756
|
+
/** How a matching term is highlighted: `true` for the route's defaults, or where and how much. */
|
|
1757
|
+
interface SearchHighlight {
|
|
1758
|
+
readonly field?: string;
|
|
1759
|
+
readonly maxWords?: number;
|
|
1760
|
+
readonly minWords?: number;
|
|
1761
|
+
}
|
|
1762
|
+
/** One facet a search counts beside its hits, answered under `meta.facets` by its name. */
|
|
1763
|
+
interface SearchFacet {
|
|
1764
|
+
readonly type: 'string' | 'number' | 'date' | 'reference';
|
|
1765
|
+
/** The Field counted; a nested path such as `pricing.basePrice` is allowed. */
|
|
1766
|
+
readonly field: string;
|
|
1767
|
+
/** The most buckets a `string` or `reference` facet answers. */
|
|
1768
|
+
readonly limit?: number;
|
|
1769
|
+
/** A `number` or `date` facet's bucket edges. */
|
|
1770
|
+
readonly boundaries?: readonly number[];
|
|
1771
|
+
/** A `reference` facet's Field of the referenced entry to label each bucket with. */
|
|
1772
|
+
readonly displayField?: string;
|
|
1773
|
+
}
|
|
1774
|
+
/** The options every read of the search family takes: where, and how fresh. */
|
|
1775
|
+
interface SearchReadOptions<C extends string> {
|
|
1776
|
+
/** The locale the hits are composed in, over the client's: one code the schema declares. */
|
|
1777
|
+
readonly locale?: C;
|
|
1778
|
+
/** The dataset to search, by id or name, over the client's (Q9). */
|
|
1779
|
+
readonly dataset?: string;
|
|
1780
|
+
/** How fresh the answer must be: a feed position, or `true` for past every cache. */
|
|
1781
|
+
readonly fresh?: FreshOption;
|
|
1782
|
+
}
|
|
1783
|
+
/**
|
|
1784
|
+
* What `search(q, options)` takes.
|
|
1785
|
+
*
|
|
1786
|
+
* - `contentType` — {@link SearchScope}; every content type when left out.
|
|
1787
|
+
* - `page` and `limit` — which page, counted from 1, and how many hits a page holds (the route's
|
|
1788
|
+
* default 20, at most 100).
|
|
1789
|
+
* - `select` — the Fields each hit carries, beside `_metadata` and the scores asked for.
|
|
1790
|
+
* - `where`, `searchFields`, `sort`, `includeScore`, `explain`, `highlight`, `facets` — the search's
|
|
1791
|
+
* own, as the route documents them.
|
|
1792
|
+
*/
|
|
1793
|
+
interface SearchOptions<K extends string, E, C extends string, W, S extends readonly SearchFieldKey<E>[] | undefined = undefined> extends SearchReadOptions<C> {
|
|
1794
|
+
readonly contentType?: SearchScope<K>;
|
|
1795
|
+
/**
|
|
1796
|
+
* The conditions a hit must meet: {@link SearchWhere}, the `where` `list` takes. A condition on
|
|
1797
|
+
* `_metadata` is refused before anything is sent.
|
|
1798
|
+
*/
|
|
1799
|
+
readonly where?: W;
|
|
1800
|
+
/** The Fields the text is matched in; every text Field when left out. */
|
|
1801
|
+
readonly searchFields?: readonly SearchFieldKey<E>[];
|
|
1802
|
+
/** The Fields each hit carries (Q15). */
|
|
1803
|
+
readonly select?: S;
|
|
1804
|
+
readonly sort?: SearchSortOption<E>;
|
|
1805
|
+
/** Which page, counted from 1. */
|
|
1806
|
+
readonly page?: number;
|
|
1807
|
+
/** How many hits a page holds. */
|
|
1808
|
+
readonly limit?: number;
|
|
1809
|
+
/** Each hit's relevance as `_score`. */
|
|
1810
|
+
readonly includeScore?: boolean;
|
|
1811
|
+
/** Each hit's score broken down, for debugging relevance. */
|
|
1812
|
+
readonly explain?: boolean;
|
|
1813
|
+
readonly highlight?: boolean | SearchHighlight;
|
|
1814
|
+
/** Counts to answer beside the hits, under `meta.facets`, each by its name. */
|
|
1815
|
+
readonly facets?: Readonly<Record<string, SearchFacet>>;
|
|
1816
|
+
}
|
|
1817
|
+
/** What `autocomplete(q, options)` takes: the type and the Field to complete are required. */
|
|
1818
|
+
interface AutocompleteOptions<K extends string, E, C extends string, W> extends SearchReadOptions<C> {
|
|
1819
|
+
readonly contentType: K;
|
|
1820
|
+
/** The Field to complete, or a nested path under one (`category.name`). */
|
|
1821
|
+
readonly field: SearchFieldKey<E> | `${SearchFieldKey<E>}.${string}`;
|
|
1822
|
+
/** Tolerate typos, on a Field that is not localized. */
|
|
1823
|
+
readonly fuzzy?: boolean;
|
|
1824
|
+
/**
|
|
1825
|
+
* The conditions an entry must meet to be suggested from, on Fields that are not localized:
|
|
1826
|
+
* {@link SearchWhere}, the `where` `list` takes.
|
|
1827
|
+
*/
|
|
1828
|
+
readonly where?: W;
|
|
1829
|
+
/** How many suggestions to answer. */
|
|
1830
|
+
readonly limit?: number;
|
|
1831
|
+
}
|
|
1832
|
+
/** What `similar(id, options)` takes. */
|
|
1833
|
+
interface SimilarOptions<K extends string, E, C extends string, S extends readonly SearchFieldKey<E>[] | undefined = undefined> extends SearchReadOptions<C> {
|
|
1834
|
+
/** Which content types to find neighbours in; every content type when left out. */
|
|
1835
|
+
readonly contentType?: SearchScope<K>;
|
|
1836
|
+
/** The Fields each hit carries. */
|
|
1837
|
+
readonly select?: S;
|
|
1838
|
+
/** How many neighbours to answer. */
|
|
1839
|
+
readonly limit?: number;
|
|
1840
|
+
}
|
|
1841
|
+
/** `E` with only the Fields `select` named, and `_metadata`; `E` when nothing is selected. */
|
|
1842
|
+
type SearchSelected<E, S> = S extends readonly [] | undefined ? E : S extends readonly string[] ? E extends unknown ? Pick<E, Extract<keyof E, S[number] | '_metadata'>> : never : E;
|
|
1843
|
+
/**
|
|
1844
|
+
* What `search(q, options)` answers: the API's own `{ data, page, meta }`, the page in the page
|
|
1845
|
+
* dialect — `{ number, limit, total, pageCount }`, `total` counting every entry the search matched.
|
|
1846
|
+
*/
|
|
1847
|
+
type SearchPage<E> = Wire<'/v1/search', 'post', SearchResultItem<E>>;
|
|
1848
|
+
|
|
1849
|
+
/**
|
|
1850
|
+
* A batch as one call: `client.batch(operations, options)` (REL-2421; REL-2409 decision Q22).
|
|
1851
|
+
*
|
|
1852
|
+
* Each operation reads like the single write it batches:
|
|
1853
|
+
*
|
|
1854
|
+
* - `{ create: 'post', data, slug?, locale?, parentId?, sortOrder? }` — `content('post').create`;
|
|
1855
|
+
* - `{ update: 'post', entry, data }` — `update(entry, data)`: checked against the version the entry
|
|
1856
|
+
* you read carries, in the locale it was read in (REL-2418);
|
|
1857
|
+
* - `{ update: 'post', id, data, locale? }` — `update(id, data)`: unchecked, on purpose;
|
|
1858
|
+
* - `{ replace: 'post', entry | id, data }` — the same, replacing the entry's content in that locale.
|
|
1859
|
+
* The Content API has no single-entry replace route, so a batch is where a replace is written;
|
|
1860
|
+
* - `{ delete: 'post', entry | id }` — checked in the locale the entry was read in, or unchecked;
|
|
1861
|
+
* - `{ singleton: 'homepage', data, locale? }` — a singleton write, which creates it the first time.
|
|
1862
|
+
*
|
|
1863
|
+
* A check travels as the operation's `ifMatch`, the tag `If-Match` takes on the single-entry routes:
|
|
1864
|
+
* one header cannot guard many writes. A stale one fails that operation with `RESOURCE_CONFLICT`
|
|
1865
|
+
* naming the versions that disagreed; under `atomic: true` nothing in the batch is written.
|
|
1866
|
+
*
|
|
1867
|
+
* The call answers the API's own body, `{ data: { results, summary } }`: a batch is answered `200`
|
|
1868
|
+
* whatever its operations did, and each result says what happened to its operation — an `outcome`,
|
|
1869
|
+
* or an `error` naming the refusal. Nothing is thrown for an operation that failed.
|
|
1870
|
+
*/
|
|
1871
|
+
|
|
1872
|
+
/** The content types an operation may name: the schema's, or any where no schema types the client. */
|
|
1873
|
+
type ContentTypeName<S> = S extends AnySchemaDefinition ? SchemaContentTypeNames<S> : string;
|
|
1874
|
+
/** The singletons a write may name: the schema's, or any where no schema types the client. */
|
|
1875
|
+
type SingletonName<S> = S extends AnySchemaDefinition ? SchemaSingletonNames<S> : string;
|
|
1876
|
+
/** The locale codes an operation may name: the schema's, or any. */
|
|
1877
|
+
type LocaleName<S> = S extends AnySchemaDefinition ? SchemaLocaleCodes<S> : string;
|
|
1878
|
+
/** An entry of content type `K`, as the schema declares it. */
|
|
1879
|
+
type EntryOf$1<S, K> = S extends AnySchemaDefinition ? InferContentType<S['contentTypes'][K & keyof S['contentTypes']], S> : BaseContent;
|
|
1880
|
+
/** The fields a singleton write sends: some of what the singleton declares, or any. */
|
|
1881
|
+
type SingletonData<S, K> = S extends AnySchemaDefinition ? Partial<Omit<InferSingleton<S['singletonContentTypes'][K & keyof S['singletonContentTypes']], S>, '_metadata'>> : {
|
|
1882
|
+
readonly [field: string]: unknown;
|
|
1883
|
+
};
|
|
1884
|
+
/** Where a written entry sits: every write may move it, as the single writes do. */
|
|
1885
|
+
interface Placement {
|
|
1886
|
+
readonly slug?: string;
|
|
1887
|
+
readonly parentId?: string;
|
|
1888
|
+
readonly sortOrder?: number;
|
|
1889
|
+
}
|
|
1890
|
+
/**
|
|
1891
|
+
* Which entry an update or replace writes: the entry you read — checked, in the locale it was read
|
|
1892
|
+
* in, so it takes no `locale` — or an id, unchecked, in the locale it names or the client's.
|
|
1893
|
+
*/
|
|
1894
|
+
type BatchTarget<L extends string> = {
|
|
1895
|
+
readonly entry: ReadEntry;
|
|
1896
|
+
readonly id?: never;
|
|
1897
|
+
readonly locale?: never;
|
|
1898
|
+
} | {
|
|
1899
|
+
readonly id: string;
|
|
1900
|
+
readonly entry?: never;
|
|
1901
|
+
readonly locale?: L;
|
|
1902
|
+
};
|
|
1903
|
+
/** Which entry a delete removes: the entry you read, checked, or an id, unchecked. */
|
|
1904
|
+
type BatchDeleteTarget = {
|
|
1905
|
+
readonly entry: ReadEntry;
|
|
1906
|
+
readonly id?: never;
|
|
1907
|
+
} | {
|
|
1908
|
+
readonly id: string;
|
|
1909
|
+
readonly entry?: never;
|
|
1910
|
+
};
|
|
1911
|
+
/** The verbs an operation is named by, exactly one per operation. */
|
|
1912
|
+
declare const VERBS: readonly ["create", "update", "replace", "delete", "singleton"];
|
|
1913
|
+
type Verb = (typeof VERBS)[number];
|
|
1914
|
+
/**
|
|
1915
|
+
* Every verb but `V`, absent. Each operation then names all five, so the one present is the union's
|
|
1916
|
+
* discriminant and a mistake is reported against the operation it was meant as.
|
|
1917
|
+
*/
|
|
1918
|
+
type Only<V extends Verb> = {
|
|
1919
|
+
readonly [Other in Exclude<Verb, V>]?: never;
|
|
1920
|
+
};
|
|
1921
|
+
/** A create of content type `K`. */
|
|
1922
|
+
type BatchCreate<K extends string, T extends BaseContent, L extends string> = Placement & Only<'create'> & {
|
|
1923
|
+
readonly create: K;
|
|
1924
|
+
readonly data: CreateInput<T>;
|
|
1925
|
+
readonly locale?: L;
|
|
1926
|
+
};
|
|
1927
|
+
/** An update of an entry of content type `K`: its fields merged into the entry. */
|
|
1928
|
+
type BatchUpdate<K extends string, T extends BaseContent, L extends string> = Placement & BatchTarget<L> & Only<'update'> & {
|
|
1929
|
+
readonly update: K;
|
|
1930
|
+
readonly data: UpdateInput<T>;
|
|
1931
|
+
};
|
|
1932
|
+
/** A replace of an entry of content type `K`: fields left out are cleared in that locale. */
|
|
1933
|
+
type BatchReplace<K extends string, T extends BaseContent, L extends string> = Placement & BatchTarget<L> & Only<'replace'> & {
|
|
1934
|
+
readonly replace: K;
|
|
1935
|
+
readonly data: CreateInput<T>;
|
|
1936
|
+
};
|
|
1937
|
+
/** A delete of an entry of content type `K`. */
|
|
1938
|
+
type BatchDelete<K extends string> = BatchDeleteTarget & Only<'delete'> & {
|
|
1939
|
+
readonly delete: K;
|
|
1940
|
+
};
|
|
1941
|
+
/** A write to singleton `K`, which creates it the first time. */
|
|
1942
|
+
type BatchSingletonWrite<K extends string, D, L extends string> = Only<'singleton'> & {
|
|
1943
|
+
readonly singleton: K;
|
|
1944
|
+
readonly data: D;
|
|
1945
|
+
readonly locale?: L;
|
|
1946
|
+
};
|
|
1947
|
+
/**
|
|
1948
|
+
* One operation of `client.batch(operations)`, each content type's `data` typed by what it declares
|
|
1949
|
+
* where a schema types the client.
|
|
1950
|
+
*/
|
|
1951
|
+
type BatchWrite<S extends AnySchemaDefinition | undefined = undefined> = {
|
|
1952
|
+
[K in ContentTypeName<S>]: BatchCreate<K, EntryOf$1<S, K>, LocaleName<S>> | BatchUpdate<K, EntryOf$1<S, K>, LocaleName<S>> | BatchReplace<K, EntryOf$1<S, K>, LocaleName<S>> | BatchDelete<K>;
|
|
1953
|
+
}[ContentTypeName<S>] | {
|
|
1954
|
+
[K in SingletonName<S>]: BatchSingletonWrite<K, SingletonData<S, K>, LocaleName<S>>;
|
|
1955
|
+
}[SingletonName<S>];
|
|
1956
|
+
/** How a batch runs. Each is the API's default when left out. */
|
|
1957
|
+
interface BatchWriteOptions {
|
|
1958
|
+
/**
|
|
1959
|
+
* One transaction: every operation lands or none does. The API takes it only when every operation
|
|
1960
|
+
* is the same kind of write and none creates a singleton (`REQUEST_INVALID` otherwise).
|
|
1961
|
+
*/
|
|
1962
|
+
readonly atomic?: boolean;
|
|
1963
|
+
/** Run every check a real write runs, and write nothing. */
|
|
1964
|
+
readonly dryRun?: boolean;
|
|
1965
|
+
/** Stop at the first operation that fails; the rest are answered `skipped`. Not with `atomic`. */
|
|
1966
|
+
readonly stopOnError?: boolean;
|
|
1967
|
+
/** `false` answers each written entry by id alone instead of reading it back. */
|
|
1968
|
+
readonly returnData?: boolean;
|
|
1969
|
+
/** The dataset to write, by id or name, over the client's. */
|
|
1970
|
+
readonly dataset?: string;
|
|
1971
|
+
}
|
|
1972
|
+
/**
|
|
1973
|
+
* One batch item, as the committed document describes the `multi` kind: an `outcome` —
|
|
1974
|
+
* `created`, `updated`, `unchanged`, `duplicate` or `skipped` — or an `error` naming the refusal.
|
|
1975
|
+
*/
|
|
1976
|
+
type BatchOperationResult = components['schemas']['MultiPayload']['results'][number];
|
|
1977
|
+
/** A batch's counts, from the contract. */
|
|
1978
|
+
type BatchSummary = components['schemas']['MultiSummary'];
|
|
1979
|
+
/** The batch payload: `{ results, summary }`. */
|
|
1980
|
+
type BatchResponse = components['schemas']['MultiPayload'];
|
|
1981
|
+
/** What `client.batch(operations)` answers: the API's body, `{ data: { results, summary } }`. */
|
|
1982
|
+
type BatchBody = Wire<'/v1/batch', 'post', BatchResponse>;
|
|
1983
|
+
|
|
1984
|
+
/**
|
|
1985
|
+
* Ad-hoc and named queries as single calls: `client.query(…)` (REL-2421; REL-2409 decision Q22).
|
|
1986
|
+
*
|
|
1987
|
+
* - `client.query({ from, … })` runs a query inline. It takes `list`'s option names, typed from the
|
|
1988
|
+
* same option types — `include`, `select`, `sort`, `limit`, `page`, `locale`, `dataset`, `fresh` —
|
|
1989
|
+
* plus its own: `aggregate`, `params` and `fullText`. `where` is `list`'s, typed as `list` types
|
|
1990
|
+
* it (`{ views: { $gte: 10 } }`): the query route reads the `where` a list takes (REL-2439,
|
|
1991
|
+
* REL-2409 decision D11), and refuses the bare form a saved query used to declare.
|
|
1992
|
+
* - `client.query(name, { params, … })` runs the saved query the schema declares under `name`, by
|
|
1993
|
+
* that name alone (`POST /v1/queries/{name}`, D9). Its parameters are typed from the declaration
|
|
1994
|
+
* ({@link InferQueryParams}): a name it does not declare does not compile, and the API refuses it
|
|
1995
|
+
* too, naming it.
|
|
1996
|
+
*
|
|
1997
|
+
* Both answer the API's body, `{ data: { result, metadata } }`.
|
|
1998
|
+
*
|
|
1999
|
+
* The query route pages by `limit` and `offset` and answers no cursor, so a query pages by number:
|
|
2000
|
+
* `page` with `limit`, sent as the offset that page starts at. There is no `cursor`.
|
|
2001
|
+
*
|
|
2002
|
+
*/
|
|
2003
|
+
|
|
2004
|
+
/** The content types a query reads: the schema's, or any where no schema types the client. */
|
|
2005
|
+
type QueryTypeName<S> = S extends AnySchemaDefinition ? SchemaContentTypeNames<S> : string;
|
|
2006
|
+
/** An entry of content type `K`, as the schema declares it. */
|
|
2007
|
+
type EntryOf<S, K> = S extends AnySchemaDefinition ? InferContentType<S['contentTypes'][K & keyof S['contentTypes']], S> : BaseContent;
|
|
2008
|
+
/** A read of content type `K`, which checks its include paths and names its locales. */
|
|
2009
|
+
type ReadingOf<S, K> = S extends AnySchemaDefinition ? ContentReading<S['contentTypes'][K & keyof S['contentTypes']], S> : UntypedReading<BaseContent>;
|
|
2010
|
+
/** The locale codes a query may name: the schema's, or any. */
|
|
2011
|
+
type QueryLocale<S> = S extends AnySchemaDefinition ? SchemaLocaleCodes<S> : string;
|
|
2012
|
+
/** The saved queries a client may run by name: the schema's, or any. */
|
|
2013
|
+
type QueryName<S> = S extends AnySchemaDefinition ? SchemaQueryNames<S> : string;
|
|
2014
|
+
/** The parameters saved query `N` declares; any, where no schema types the client. */
|
|
2015
|
+
type NamedQueryParams<S, N> = S extends AnySchemaDefinition ? InferQueryParams<S['queries'][N & keyof S['queries']]> : AnyQueryParameters;
|
|
2016
|
+
/**
|
|
2017
|
+
* What an ad-hoc query computes over the entries it fetched: the groups and metrics of the query
|
|
2018
|
+
* route's `aggregate` block. Its source is `from`.
|
|
2019
|
+
*/
|
|
2020
|
+
type QueryAggregate = Omit<AggregateBlock, 'source'>;
|
|
2021
|
+
/** How a query pages: by number, with `limit` its size — or not at all. */
|
|
2022
|
+
type QueryPaging<T extends BaseContent, R extends Reading> = {
|
|
2023
|
+
readonly page?: never;
|
|
2024
|
+
} | {
|
|
2025
|
+
/** Which page, from 1: sent as the offset it starts at, `(page - 1) * limit`. */
|
|
2026
|
+
readonly page: number;
|
|
2027
|
+
readonly limit: NonNullable<ListReadOptions<T, R, []>['limit']>;
|
|
2028
|
+
};
|
|
2029
|
+
/** `select` or `include`: the route refuses an include path beside a selection. */
|
|
2030
|
+
type SelectOrInclude<R extends Reading, P extends readonly string[], Sel> = {
|
|
2031
|
+
readonly select?: Sel;
|
|
2032
|
+
readonly include?: undefined;
|
|
2033
|
+
} | {
|
|
2034
|
+
readonly select?: undefined;
|
|
2035
|
+
readonly include?: CheckedIncludes<R, P>;
|
|
2036
|
+
};
|
|
2037
|
+
/**
|
|
2038
|
+
* What `client.query({ from, … })` takes: `list`'s options, typed as `list` types them, and the
|
|
2039
|
+
* query's own.
|
|
2040
|
+
*/
|
|
2041
|
+
type AdhocQuery<K extends string, T extends BaseContent, R extends Reading, P extends readonly string[], Sel extends readonly FieldKey<T>[] | undefined, A extends QueryAggregate | undefined> = {
|
|
2042
|
+
/** The content type the query reads. */
|
|
2043
|
+
readonly from: K;
|
|
2044
|
+
/**
|
|
2045
|
+
* The conditions an entry must meet: the `where` `list` takes, typed as `list` types it — a value,
|
|
2046
|
+
* a list, or `$`-prefixed operators (`{ views: { $gte: 10 } }`). A condition on `_metadata` is
|
|
2047
|
+
* refused before anything is sent.
|
|
2048
|
+
*/
|
|
2049
|
+
readonly where?: ListReadOptions<T, R, P>['where'];
|
|
2050
|
+
/** The order, by Field or system date, the key order the priority. */
|
|
2051
|
+
readonly sort?: SortOption<T>;
|
|
2052
|
+
/** How many entries the query answers — a page's size where `page` names one. */
|
|
2053
|
+
readonly limit?: ListReadOptions<T, R, P>['limit'];
|
|
2054
|
+
/** The one locale the entries are composed in, over the client's. */
|
|
2055
|
+
readonly locale?: ReadLocale<R>;
|
|
2056
|
+
/** The dataset to read, by id or name, over the client's. */
|
|
2057
|
+
readonly dataset?: string;
|
|
2058
|
+
/** How fresh the answer must be: a feed position, or `true` for past every cache. */
|
|
2059
|
+
readonly fresh?: FreshOption;
|
|
2060
|
+
/** Groups and metrics computed over the entries fetched; the answer is then their rows. */
|
|
2061
|
+
readonly aggregate?: A;
|
|
2062
|
+
/** Values for the `${param}` expressions the query writes. */
|
|
2063
|
+
readonly params?: {
|
|
2064
|
+
readonly [name: string]: QueryParameterValue;
|
|
2065
|
+
};
|
|
2066
|
+
/** A text search over the Fields it names, beside the fetch. */
|
|
2067
|
+
readonly fullText?: FullTextSearch;
|
|
2068
|
+
} & QueryPaging<T, R> & SelectOrInclude<R, P, Sel>;
|
|
2069
|
+
/** What `client.query(name, options)` takes beside the parameters. */
|
|
2070
|
+
interface NamedQueryReadOptions<L extends string> {
|
|
2071
|
+
/** The one locale the query composes in, over the client's. */
|
|
2072
|
+
readonly locale?: L;
|
|
2073
|
+
/** The dataset to read, by id or name, over the client's. */
|
|
2074
|
+
readonly dataset?: string;
|
|
2075
|
+
/**
|
|
2076
|
+
* `true` reads past every cache. The route is not edge-kept, so a feed position has no cached
|
|
2077
|
+
* answer to be fresher than and is not offered.
|
|
2078
|
+
*/
|
|
2079
|
+
readonly fresh?: true;
|
|
2080
|
+
}
|
|
2081
|
+
/** What `client.query(name, options)` takes: `params` required where the query requires one. */
|
|
2082
|
+
type NamedQueryOptions<Params, L extends string> = NamedQueryReadOptions<L> & (Record<never, never> extends Params ? {
|
|
2083
|
+
readonly params?: Params;
|
|
2084
|
+
} : {
|
|
2085
|
+
readonly params: Params;
|
|
2086
|
+
});
|
|
2087
|
+
/** The options argument, which may be left out only where no parameter is required. */
|
|
2088
|
+
type NamedQueryArgs<Params, L extends string> = Record<never, never> extends Params ? [options?: NamedQueryOptions<Params, L>] : [options: NamedQueryOptions<Params, L>];
|
|
2089
|
+
/**
|
|
2090
|
+
* One metric, as the Query DSL writes it: `<field>.<fn>`, where `fn` is `count`, `sum`, `avg`,
|
|
2091
|
+
* `min`, `max`, `first` or `last` — `'id.count'`, `'pricing.basePrice.avg'`. The service validates
|
|
2092
|
+
* every metric against that form (`INVALID_AGGREGATION_EXPRESSION`); `count` ignores the field it
|
|
2093
|
+
* names (REL-1470).
|
|
2094
|
+
*/
|
|
2095
|
+
type AggregateMetric = `${string}.${'count' | 'sum' | 'avg' | 'min' | 'max' | 'first' | 'last'}`;
|
|
2096
|
+
/** How a query ran, as the query routes report it beside the records. */
|
|
2097
|
+
interface QueryMetadata {
|
|
2098
|
+
/** Query identifier */
|
|
2099
|
+
queryId: string;
|
|
2100
|
+
/** Execution time in milliseconds */
|
|
2101
|
+
executionTimeMs: number;
|
|
2102
|
+
/** Number of results */
|
|
2103
|
+
resultCount: number;
|
|
2104
|
+
/** ISO timestamp */
|
|
2105
|
+
timestamp: string;
|
|
2106
|
+
/** Whether result was from cache */
|
|
2107
|
+
cacheHit?: boolean;
|
|
2108
|
+
/** Generated pipeline (if debug enabled) */
|
|
2109
|
+
pipeline?: unknown[];
|
|
2110
|
+
/** Resolved parameters (if debug enabled) */
|
|
2111
|
+
parameters?: Record<string, unknown>;
|
|
2112
|
+
/** Validation warnings */
|
|
2113
|
+
warnings?: string[];
|
|
2114
|
+
}
|
|
2115
|
+
/** The API's body for a query: the records it produced and how it ran. */
|
|
2116
|
+
interface QueryAnswer<T> {
|
|
2117
|
+
readonly data: {
|
|
2118
|
+
/** The records the query produced. */
|
|
2119
|
+
readonly result: T;
|
|
2120
|
+
readonly metadata: QueryMetadata;
|
|
2121
|
+
};
|
|
2122
|
+
}
|
|
2123
|
+
/**
|
|
2124
|
+
* One record of an ad-hoc query: an aggregate's row, the selected Fields of an entry, or the entry
|
|
2125
|
+
* as its include paths read it.
|
|
2126
|
+
*/
|
|
2127
|
+
type AdhocRecord<R extends Reading, P extends readonly string[], Sel, A> = A extends QueryAggregate ? Readonly<Record<string, unknown>> : Sel extends readonly string[] ? Pick<ReadWith<R, P[number]>, Extract<keyof ReadWith<R, P[number]>, Sel[number]>> : ReadWith<R, P[number]>;
|
|
2128
|
+
/**
|
|
2129
|
+
* `client.query`: callable two ways, beside the chained client it was before.
|
|
2130
|
+
*
|
|
2131
|
+
* Each way is read off a method (`['run']`), so its parameters compare as a method's do: a client
|
|
2132
|
+
* typed by one schema stays assignable to one typed by a wider one, as its other methods let it be.
|
|
2133
|
+
*/
|
|
2134
|
+
type QueryCall<S extends AnySchemaDefinition | undefined> = AdhocQueryCall<S> & NamedQueryCall<S>;
|
|
2135
|
+
/** `client.query({ from, … })`. */
|
|
2136
|
+
type AdhocQueryCall<S extends AnySchemaDefinition | undefined> = {
|
|
2137
|
+
/**
|
|
2138
|
+
* Run a query inline: `list`'s options plus `aggregate`, `params` and `fullText`. Answers the
|
|
2139
|
+
* API's body, its `result` the entries — or the aggregate's rows.
|
|
2140
|
+
*
|
|
2141
|
+
* @example
|
|
2142
|
+
* ```typescript
|
|
2143
|
+
* const { data } = await relyio.query({
|
|
2144
|
+
* from: 'post',
|
|
2145
|
+
* where: { views: { $gte: 100 } },
|
|
2146
|
+
* select: ['title', 'views'],
|
|
2147
|
+
* sort: { views: 'desc' },
|
|
2148
|
+
* limit: 10,
|
|
2149
|
+
* page: 2,
|
|
2150
|
+
* })
|
|
2151
|
+
* ```
|
|
2152
|
+
*/
|
|
2153
|
+
run<K extends QueryTypeName<S>, const P extends readonly string[] = [], const Sel extends readonly FieldKey<EntryOf<S, K>>[] | undefined = undefined, const A extends QueryAggregate | undefined = undefined>(query: AdhocQuery<K, EntryOf<S, K>, ReadingOf<S, K>, P, Sel, A>): Promise<QueryAnswer<AdhocRecord<ReadingOf<S, K>, P, Sel, A>[]>>;
|
|
2154
|
+
}['run'];
|
|
2155
|
+
/** `client.query(name, { params, … })`. */
|
|
2156
|
+
type NamedQueryCall<S extends AnySchemaDefinition | undefined> = {
|
|
2157
|
+
/**
|
|
2158
|
+
* Run the saved query the deployed schema declares under `name`, with the parameters it declares
|
|
2159
|
+
* (`POST /v1/queries/{name}`, D9). Answers the API's body.
|
|
2160
|
+
*
|
|
2161
|
+
* It needs only the name: a client created without a schema runs it too, its parameters untyped.
|
|
2162
|
+
* A schema the client is typed by (`createClient({ schema })`, or `Register`) types the name and
|
|
2163
|
+
* the parameters. The API refuses a parameter the query does not declare, naming it.
|
|
2164
|
+
*
|
|
2165
|
+
* @example
|
|
2166
|
+
* ```typescript
|
|
2167
|
+
* const { data } = await relyio.query('postsByTag', { params: { tag: 'birds' } })
|
|
2168
|
+
* ```
|
|
2169
|
+
*/
|
|
2170
|
+
run<N extends QueryName<S>>(name: N, ...options: NamedQueryArgs<NamedQueryParams<S, N>, QueryLocale<S>>): Promise<QueryAnswer<unknown>>;
|
|
2171
|
+
}['run'];
|
|
2172
|
+
|
|
2173
|
+
/**
|
|
2174
|
+
* Content and resource errors.
|
|
2175
|
+
*
|
|
2176
|
+
* `ContentTypeNotFoundError` and `ContentNotFoundError` were deleted at API5.
|
|
2177
|
+
* The mapper reached them by sniffing `CONTENT_TYPE_NOT_FOUND` and
|
|
2178
|
+
* `CONTENT_NOT_FOUND`, neither of which is in the closed vocabulary, so neither
|
|
2179
|
+
* class was reachable. Both cases are `RESOURCE_NOT_FOUND`, whose declared
|
|
2180
|
+
* `details` are `{ kind, id? }` — the same two facts, from the one code a route
|
|
2181
|
+
* can actually declare.
|
|
2182
|
+
*/
|
|
2183
|
+
declare class ResourceNotFoundError extends SDKError {
|
|
2184
|
+
readonly code = "RESOURCE_NOT_FOUND";
|
|
2185
|
+
readonly retryable = false;
|
|
2186
|
+
constructor(message?: string, statusCode?: number, cause?: Error, context?: Record<string, unknown>);
|
|
2187
|
+
getUserMessage(): string;
|
|
2188
|
+
}
|
|
2189
|
+
declare class ResourceConflictError extends SDKError {
|
|
2190
|
+
readonly code = "RESOURCE_CONFLICT";
|
|
2191
|
+
readonly retryable = false;
|
|
2192
|
+
constructor(message?: string, statusCode?: number, cause?: Error, context?: Record<string, unknown>);
|
|
2193
|
+
getUserMessage(): string;
|
|
2194
|
+
}
|
|
2195
|
+
declare class ResourceExpiredError extends SDKError {
|
|
2196
|
+
readonly code = "RESOURCE_EXPIRED";
|
|
2197
|
+
readonly retryable = false;
|
|
2198
|
+
constructor(message?: string, statusCode?: number, cause?: Error, context?: Record<string, unknown>);
|
|
2199
|
+
getUserMessage(): string;
|
|
2200
|
+
}
|
|
2201
|
+
/**
|
|
2202
|
+
* "Did the service say this resource does not exist?"
|
|
2203
|
+
*
|
|
2204
|
+
* Reads the **code**, not the status. Four contract codes are 4xx on a read and
|
|
2205
|
+
* only one of them means "not there"; the incumbent checked `statusCode === 404`
|
|
2206
|
+
* on the thrown value, and a second arm duck-typed a plain object for the same
|
|
2207
|
+
* number so a hand-written test double would pass. Both are the branch
|
|
2208
|
+
* `INV-API-CLIENT-IS-DERIVED` forbids where a code exists — and the second one
|
|
2209
|
+
* only existed because the tests faked the boundary (rule 20). They now stub
|
|
2210
|
+
* `fetch`, so the double is gone with the branch.
|
|
2211
|
+
*/
|
|
2212
|
+
declare function isResourceNotFound(error: unknown): boolean;
|
|
2213
|
+
/**
|
|
2214
|
+
* "Did the service say the **entry** does not exist?" — and not its content type, its schema or its
|
|
2215
|
+
* dataset (REL-2415, REL-2409 decision Q12).
|
|
2216
|
+
*
|
|
2217
|
+
* `RESOURCE_NOT_FOUND` carries `details.kind`, which the mapper keeps as `context.resourceType`. A
|
|
2218
|
+
* read that answered `null` for every kind made a content type nobody deployed look like a missing
|
|
2219
|
+
* page, so `get` answers `null` only for this one and throws the rest as they arrived.
|
|
2220
|
+
*/
|
|
2221
|
+
declare function isMissingEntry(error: unknown): boolean;
|
|
2222
|
+
|
|
2223
|
+
/**
|
|
2224
|
+
* `relyio.changes()` — the changes feed as a typed long-poll iterator
|
|
2225
|
+
* (`sdk-surfaces-spec.md` decisions 16–18).
|
|
2226
|
+
*
|
|
2227
|
+
* Long-poll only, on purpose. `GET /v1/changes` is an ordinary cursor-paged
|
|
2228
|
+
* collection the committed document declares, so each poll is one `send` over
|
|
2229
|
+
* the one HTTP client (`INV-SS-ONE-CORE`, `INV-API-CLIENT-IS-DERIVED`). The SSE
|
|
2230
|
+
* branch the route also serves would be a second transport path inside that
|
|
2231
|
+
* client, and `parseResponse` buffers a whole body; it stays for `EventSource`
|
|
2232
|
+
* and curl consumers.
|
|
2233
|
+
*/
|
|
2234
|
+
|
|
2235
|
+
type PollChanges = paths['/v1/changes']['get'];
|
|
2236
|
+
/**
|
|
2237
|
+
* The 200 body the document declares, read from its `application/json` content
|
|
2238
|
+
* **by name**. The route also negotiates `text/event-stream`; naming the JSON
|
|
2239
|
+
* content is what keeps that alternate from changing this type when the
|
|
2240
|
+
* document gains it.
|
|
2241
|
+
*/
|
|
2242
|
+
type ChangesPageBody = PollChanges['responses'][200]['content']['application/json'];
|
|
2243
|
+
/**
|
|
2244
|
+
* One change, as the committed document describes an item of the feed's `data`
|
|
2245
|
+
* (`INV-SS-CHANGE-IS-IDS`: ids, content type, locale, kind and cursor — never a
|
|
2246
|
+
* document).
|
|
2247
|
+
*
|
|
2248
|
+
* Read from the document, never written here: the api's route row names the
|
|
2249
|
+
* `Change` component schema, and a change to it reaches this type when the
|
|
2250
|
+
* document is regenerated. `changes.types.ts` holds it equal to the document's
|
|
2251
|
+
* item, and fails if it is ever `unknown` again.
|
|
2252
|
+
*/
|
|
2253
|
+
type Change = ChangesPageBody['data'][number];
|
|
2254
|
+
/**
|
|
2255
|
+
* A change as `relyio.changes()` and `relyio.liveChanges()` deliver it: the document's
|
|
2256
|
+
* {@link Change}, and `fresh`.
|
|
2257
|
+
*
|
|
2258
|
+
* `fresh` is the feed position a refetch for this change names, as the one query term the API
|
|
2259
|
+
* declares on every read its edge may keep (`?_fresh`, `sdk-surfaces-spec.md` decision 32): hand it
|
|
2260
|
+
* to the read as its `fresh` option — `get(id, { fresh: change.fresh })`, `list({ fresh })`,
|
|
2261
|
+
* `search(q, { fresh })`, a singleton's `get({ fresh })`. A position no request named before the change is a key no cache holds a
|
|
2262
|
+
* body under, so the read is answered after the change — at the edge and in the browser, whether
|
|
2263
|
+
* or not the change's purge has reached them — and every later read naming it shares that answer.
|
|
2264
|
+
*
|
|
2265
|
+
* It is at or after the change, never before: on `/v1/changes` the change's own cursor, the ledger
|
|
2266
|
+
* row it came from; at the live edge the cursor of the batch it arrived in. It is not always
|
|
2267
|
+
* `cursor`: at the edge an earlier change of a batch carries the position **before** the batch as
|
|
2268
|
+
* its `cursor`, so a failure part-way through replays the batch, and a refetch naming that position
|
|
2269
|
+
* would be answered from before the change.
|
|
2270
|
+
*/
|
|
2271
|
+
type FeedChange = Change & {
|
|
2272
|
+
/** The feed position a refetch for this change names (`?_fresh`); at or after the change. */
|
|
2273
|
+
readonly fresh: string;
|
|
2274
|
+
};
|
|
2275
|
+
/**
|
|
2276
|
+
* Why a cursor could no longer be replayed, or why everything must be refetched. `/v1/changes`
|
|
2277
|
+
* names `retention` and `dataset-replaced`; the live edge's `feed.resync` may name any reason its
|
|
2278
|
+
* wire declares (`VisitorResyncReason` in `@relyio/api-contract`, read from there rather than
|
|
2279
|
+
* restated). `unknown` when the message named none.
|
|
2280
|
+
*/
|
|
2281
|
+
type ChangesExpiredReason = VisitorResyncReason | 'unknown';
|
|
2282
|
+
/**
|
|
2283
|
+
* `RESOURCE_EXPIRED` from the changes feed: the cursor names a position the
|
|
2284
|
+
* ledger can no longer replay from (`INV-SS-CURSOR-REPLAYS`). The consumer
|
|
2285
|
+
* resyncs from the read path and starts again without a cursor.
|
|
2286
|
+
*
|
|
2287
|
+
* A `ResourceExpiredError`, so code that already catches that still does. The
|
|
2288
|
+
* refusal declares no details, so `reason` is read from the message the api
|
|
2289
|
+
* writes (`apps/services/api/src/changes/changes-request.ts` `refusalOf`).
|
|
2290
|
+
*
|
|
2291
|
+
* The live edge's `feed.resync` — a socket's frame or a poll's answer — is the
|
|
2292
|
+
* same instruction and arrives as the same error ({@link ChangesExpiredError.resync}),
|
|
2293
|
+
* so `onExpired` means one thing on both transports.
|
|
2294
|
+
*/
|
|
2295
|
+
declare class ChangesExpiredError extends ResourceExpiredError {
|
|
2296
|
+
readonly reason: ChangesExpiredReason;
|
|
2297
|
+
/**
|
|
2298
|
+
* The position the feed resynced to, which a refetch of everything names as `?_fresh`, as a
|
|
2299
|
+
* change's {@link FeedChange.fresh} is named: the head a `/v1/changes` resync took, or the cursor
|
|
2300
|
+
* the edge's `feed.resync` sent. `undefined` when there was no resync to take one — the error was
|
|
2301
|
+
* thrown (`onExpired: 'throw'`) — and on an error a caller built itself.
|
|
2302
|
+
*/
|
|
2303
|
+
readonly fresh: string | undefined;
|
|
2304
|
+
/** The base class's signature, so `withContext` can rebuild one; then the position resynced to. */
|
|
2305
|
+
constructor(message: string, statusCode?: number, cause?: Error, context?: Record<string, unknown>, fresh?: string);
|
|
2306
|
+
/** The base class rebuilds from its four arguments, which would drop the position resynced to. */
|
|
2307
|
+
withContext(context: Record<string, unknown>): this;
|
|
2308
|
+
/** `/v1/changes`' refusal, with the head the feed resynced to when it did. */
|
|
2309
|
+
static of(expired: ResourceExpiredError, fresh?: string): ChangesExpiredError;
|
|
2310
|
+
/** The live edge's `feed.resync`: refetch everything, then continue from the cursor it sent. */
|
|
2311
|
+
static resync(reason: VisitorResyncReason, cursor: string): ChangesExpiredError;
|
|
2312
|
+
}
|
|
2313
|
+
interface ChangesOptions {
|
|
2314
|
+
/**
|
|
2315
|
+
* A `page.nextCursor` — or a change's `cursor` — from an earlier poll. Omitted,
|
|
2316
|
+
* the first poll answers the dataset's current position and no changes, at
|
|
2317
|
+
* once, and the feed starts from there.
|
|
2318
|
+
*/
|
|
2319
|
+
since?: string;
|
|
2320
|
+
/** Content types to keep. Omitted keeps every type; an empty list is refused (400). */
|
|
2321
|
+
types?: readonly string[];
|
|
2322
|
+
/**
|
|
2323
|
+
* Entry ids to keep. Omitted keeps every entry. The api refuses more than it
|
|
2324
|
+
* allows (100 today) as `REQUEST_INVALID`, thrown from the iterator as an
|
|
2325
|
+
* `InvalidPayloadError` on the first poll; the SDK does not restate the
|
|
2326
|
+
* cap, so it cannot drift from the service's.
|
|
2327
|
+
*/
|
|
2328
|
+
ids?: readonly string[];
|
|
2329
|
+
/** Ledger rows one poll scans, 1 to 500; the api defaults to 100. */
|
|
2330
|
+
limit?: number;
|
|
2331
|
+
/**
|
|
2332
|
+
* Ends the iteration. An abort — in flight, between polls, or while waiting
|
|
2333
|
+
* out a `Retry-After` — makes the `for await` **complete**, with no further
|
|
2334
|
+
* request and no `AbortError` thrown into the loop.
|
|
2335
|
+
*/
|
|
2336
|
+
signal?: AbortSignal;
|
|
2337
|
+
/**
|
|
2338
|
+
* What to do when the cursor has expired.
|
|
2339
|
+
*
|
|
2340
|
+
* - `'throw'` (default) — the iterator throws {@link ChangesExpiredError}.
|
|
2341
|
+
* - `'resync'` — restart from the dataset's current position and keep going.
|
|
2342
|
+
* - a function — resync, and call it with the error once the new position is
|
|
2343
|
+
* taken and before any change after it is yielded. This is the observable
|
|
2344
|
+
* form: whatever the consumer derived from the feed may be stale, so the
|
|
2345
|
+
* function is where it invalidates everything — naming the error's `fresh`,
|
|
2346
|
+
* the new position, on every refetch. The iterator waits for it.
|
|
2347
|
+
*/
|
|
2348
|
+
onExpired?: 'throw' | 'resync' | ((expired: ChangesExpiredError) => void | Promise<void>);
|
|
2349
|
+
/**
|
|
2350
|
+
* Called once, when the feed was opened without `since`, with the position it starts from: the
|
|
2351
|
+
* first poll's answer, the dataset's head — before the next poll is sent. Read what you show
|
|
2352
|
+
* **after** it and a write your read did not see is after the position, so this feed delivers it,
|
|
2353
|
+
* with no second request to take the position first (`changesHead()` is that request) (REL-1513).
|
|
2354
|
+
*
|
|
2355
|
+
* `followChanges()` passes it (`ChangeSourceOptions.reached`), so a feed that drops before its first
|
|
2356
|
+
* change reopens from there rather than re-reading everything. Never called with a later empty
|
|
2357
|
+
* page's cursor: filtered, that cursor is past rows the filter hid, and a caller that widens the
|
|
2358
|
+
* filter must not reopen from it.
|
|
2359
|
+
*/
|
|
2360
|
+
reached?: (cursor: string) => void;
|
|
2361
|
+
}
|
|
2362
|
+
interface ChangesHeadOptions {
|
|
2363
|
+
/** Cancels the request; the promise then rejects. */
|
|
2364
|
+
signal?: AbortSignal;
|
|
2365
|
+
}
|
|
2366
|
+
|
|
2367
|
+
/**
|
|
2368
|
+
* `relyio.liveChanges()` — a dataset's published changes at RelyIO's live edge, for a publishable key
|
|
2369
|
+
* (`live-layer-spec.md` decision 20; LV5.2, REL-1158).
|
|
2370
|
+
*
|
|
2371
|
+
* The wire is `@relyio/api-contract`'s `live-visitor.ts`, read from there and never restated: the
|
|
2372
|
+
* path and query names, the frames and their reader, the keepalive, the poll's answer and its reader,
|
|
2373
|
+
* the poll interval, and what each close code and each refused poll means a client does next.
|
|
2374
|
+
*
|
|
2375
|
+
* **One open, two ways in.** A subscription opens a WebSocket to the edge. Where it cannot — the
|
|
2376
|
+
* runtime has no `WebSocket` (Node 18 and 20), constructing one throws, the socket fails before it
|
|
2377
|
+
* opens (a network that blocks sockets), or it has not opened and been caught up within
|
|
2378
|
+
* {@link LIVE_SOCKET_OPEN_TIMEOUT_MS} (a network that holds it) — it polls the edge instead, from the
|
|
2379
|
+
* same cursor, about every {@link VISITOR_POLL_INTERVAL_MS}, and tries a socket again about once a
|
|
2380
|
+
* minute. Both carry one cursor space, so a switch either way loses nothing.
|
|
2381
|
+
*
|
|
2382
|
+
* **Where it starts.** Without `since`, the edge answers the dataset's current position before
|
|
2383
|
+
* anything else — the socket's `feed.opened`, or the first poll's answer — and the subscription hands
|
|
2384
|
+
* it to `reached`. That call is the position, with no request of its own for it: a read sent after it
|
|
2385
|
+
* is answered at or after the position, and every change after it is delivered here, so a publish the
|
|
2386
|
+
* read did not see is announced (REL-1513). The edge counts the subscription as one live connection,
|
|
2387
|
+
* whichever way in it took (`INV-LV-LIVE-IS-METERED`).
|
|
2388
|
+
*
|
|
2389
|
+
* **It ends only when its signal aborts, and throws every other end** — a socket closed by a refusal
|
|
2390
|
+
* or a deploy, a refused poll, a keepalive nobody answered — as a `LiveEdgeError` saying what to do
|
|
2391
|
+
* next, so `followChanges()` reconnects, backs off or stops by the edge's own tables. That loop is
|
|
2392
|
+
* the one reconnect policy (`INV-SS-ONE-CORE`, REL-1215); this module keeps no back-off of its own.
|
|
2393
|
+
*
|
|
2394
|
+
* **Cursors.** The edge sends one cursor after a batch of changes. The last change of a batch carries
|
|
2395
|
+
* it; each earlier one carries the position before the batch, so a failure part-way through a batch
|
|
2396
|
+
* replays the whole batch rather than skipping its rest. Delivery is at-least-once: a replay and the
|
|
2397
|
+
* live frames after it may name one change twice, and a refetch makes a repeat cost a read, never a
|
|
2398
|
+
* wrong view (`INV-SS-CHANGE-IS-IDS`).
|
|
2399
|
+
*
|
|
2400
|
+
* **What a refetch names.** Every change of a batch carries the batch's own cursor as `fresh`
|
|
2401
|
+
* (`FeedChange`), the position its refetch names as `?_fresh` — not `cursor`, which for an earlier
|
|
2402
|
+
* change is the position before the batch: the previous batch's refetches were answered under that
|
|
2403
|
+
* one, before this batch, and a cache would hand the same answer back. A `feed.resync`'s error
|
|
2404
|
+
* carries the cursor it sent the same way.
|
|
2405
|
+
*/
|
|
2406
|
+
|
|
2407
|
+
/**
|
|
2408
|
+
* How often an open socket sends the keepalive, which the edge's runtime answers without waking the
|
|
2409
|
+
* feed — the Studio's dataset room sends its own on the same interval. A socket that has heard
|
|
2410
|
+
* nothing by the next one, not even the answer, is presumed dead and reconnected: a client that only
|
|
2411
|
+
* receives never learns otherwise that a network change dropped its connection.
|
|
2412
|
+
*/
|
|
2413
|
+
declare const LIVE_KEEPALIVE_INTERVAL_MS = 30000;
|
|
2414
|
+
/**
|
|
2415
|
+
* How long a socket has to open and be caught up — the feed's `feed.opened` — before the subscription
|
|
2416
|
+
* stops waiting on it, closes it and polls instead, as it does for a socket that fails before it opens
|
|
2417
|
+
* (REL-1513).
|
|
2418
|
+
*
|
|
2419
|
+
* The poll interval, because that is what a socket is for: telling the page sooner than a poll would.
|
|
2420
|
+
* A poll is answered at once and polled again within this interval, so a socket that has not caught
|
|
2421
|
+
* up by then has already served the page worse than polling would have. It is well past a healthy
|
|
2422
|
+
* open — the door's checks and one ledger read at the edge — and half the keepalive's verdict on an
|
|
2423
|
+
* open socket that has gone quiet ({@link LIVE_KEEPALIVE_INTERVAL_MS}, twice). It bounds how long
|
|
2424
|
+
* `<RelyIOProvider live>`'s first reads wait for a position on a network that neither opens a socket
|
|
2425
|
+
* nor refuses one — a proxy that holds the upgrade — which without it they would wait on for as long
|
|
2426
|
+
* as the page is open.
|
|
2427
|
+
*/
|
|
2428
|
+
declare const LIVE_SOCKET_OPEN_TIMEOUT_MS = 15000;
|
|
2429
|
+
interface LiveChangesOptions {
|
|
2430
|
+
/**
|
|
2431
|
+
* A change's cursor from an earlier subscription. Omitted, the edge answers the dataset's current
|
|
2432
|
+
* position first and replays nothing, and the subscription hands that position to `reached` before
|
|
2433
|
+
* anything after it. Read what you show **after** that first call — a read sent before it, from a
|
|
2434
|
+
* subscription opened afterwards without `since`, leaves a publish between the two that neither
|
|
2435
|
+
* sees (REL-1513). With `followChanges()`, which hands the source its own `reached`, call both —
|
|
2436
|
+
* and let the read go if the loop fails first, so content never waits on a feed that is down:
|
|
2437
|
+
*
|
|
2438
|
+
* ```ts
|
|
2439
|
+
* let positioned!: () => void
|
|
2440
|
+
* const position = new Promise<void>(resolve => (positioned = resolve))
|
|
2441
|
+
* const following = followChanges(
|
|
2442
|
+
* from =>
|
|
2443
|
+
* relyio.liveChanges({
|
|
2444
|
+
* ...from,
|
|
2445
|
+
* ids: [id],
|
|
2446
|
+
* reached: cursor => {
|
|
2447
|
+
* from.reached(cursor)
|
|
2448
|
+
* positioned()
|
|
2449
|
+
* },
|
|
2450
|
+
* }),
|
|
2451
|
+
* { signal, onChange, onResync, onState: state => state.kind !== 'following' && positioned() },
|
|
2452
|
+
* )
|
|
2453
|
+
* await position
|
|
2454
|
+
* const post = await relyio.content('post').get(id)
|
|
2455
|
+
* ```
|
|
2456
|
+
*
|
|
2457
|
+
* The wait is bounded: a socket that has not opened and been told its position within
|
|
2458
|
+
* {@link LIVE_SOCKET_OPEN_TIMEOUT_MS} is given up for a poll, answered at once.
|
|
2459
|
+
*/
|
|
2460
|
+
since?: string;
|
|
2461
|
+
/** Content types to keep. Omitted keeps every type; an empty list is refused. At most 50. */
|
|
2462
|
+
types?: readonly string[];
|
|
2463
|
+
/** Entry ids to keep. Omitted keeps every entry; an empty list is refused. At most 100. */
|
|
2464
|
+
ids?: readonly string[];
|
|
2465
|
+
/**
|
|
2466
|
+
* Ends the subscription. An abort — while connected, while polling, or between polls — makes the
|
|
2467
|
+
* `for await` **complete**, with the socket closed, no further request and nothing thrown.
|
|
2468
|
+
*/
|
|
2469
|
+
signal?: AbortSignal;
|
|
2470
|
+
/**
|
|
2471
|
+
* What to do when the edge says to refetch everything (`feed.resync`: the cursor is too old, was
|
|
2472
|
+
* never this dataset's, or the dataset was replaced) — as `relyio.changes()` takes it: `'throw'`
|
|
2473
|
+
* (default) throws a `ChangesExpiredError`, `'resync'` carries on from the edge's position, and a
|
|
2474
|
+
* function is awaited with the error before any change after that position is yielded.
|
|
2475
|
+
*/
|
|
2476
|
+
onExpired?: ChangesOptions['onExpired'];
|
|
2477
|
+
/**
|
|
2478
|
+
* Called when the edge has the subscription in hand — its `feed.opened` frame, or an answered
|
|
2479
|
+
* poll. `followChanges()` passes it to reset its back-off; see `ChangeSourceOptions.opened`.
|
|
2480
|
+
*/
|
|
2481
|
+
opened?: () => void;
|
|
2482
|
+
/**
|
|
2483
|
+
* Called with a position reached without a change to carry it. `followChanges()` passes it so a
|
|
2484
|
+
* connection dropped before its first change reconnects from there; see
|
|
2485
|
+
* `ChangeSourceOptions.reached`. Without `since`, its first call is the position the subscription
|
|
2486
|
+
* starts from (see `since`).
|
|
2487
|
+
*/
|
|
2488
|
+
reached?: (cursor: string) => void;
|
|
2489
|
+
}
|
|
2490
|
+
|
|
2491
|
+
/**
|
|
2492
|
+
* The key's project, as `GET /v1/project` describes it: organization, project, dataset and branch,
|
|
2493
|
+
* each by id and name, and `studioUrl` — the Studio root the preview toolbar deep-links from
|
|
2494
|
+
* (`sdk-surfaces-spec.md` decision 24), or `null` when the service has no Dashboard address.
|
|
2495
|
+
*
|
|
2496
|
+
* Read from the committed document's `application/json` content by name, never written here
|
|
2497
|
+
* (`INV-API-CLIENT-IS-DERIVED`); `derived-transport.types.ts` holds the two equal.
|
|
2498
|
+
*/
|
|
2499
|
+
type Project = paths['/v1/project']['get']['responses'][200]['content']['application/json']['data'];
|
|
2500
|
+
/** What `health()` resolves: the API answered and reaches every store it reads. */
|
|
2501
|
+
interface Health {
|
|
2502
|
+
readonly status: 'ok';
|
|
2503
|
+
/** When the API answered, as an ISO 8601 string. */
|
|
2504
|
+
readonly timestamp: string;
|
|
2505
|
+
}
|
|
2506
|
+
/** One locale `locales()` reports, as the deployed schema declares it. */
|
|
2507
|
+
interface DeployedLocale {
|
|
2508
|
+
/** BCP 47 code, as a read's `locale` takes it. */
|
|
2509
|
+
readonly code: string;
|
|
2510
|
+
readonly displayName: string;
|
|
2511
|
+
/** Whether this is the default locale. Exactly one locale is. */
|
|
2512
|
+
readonly default: boolean;
|
|
2513
|
+
/** The locales a read in this one falls back to, in order. */
|
|
2514
|
+
readonly fallback?: string[];
|
|
2515
|
+
readonly direction?: 'ltr' | 'rtl';
|
|
2516
|
+
/** `false` when the locale is declared and switched off. */
|
|
2517
|
+
readonly active?: boolean;
|
|
2518
|
+
}
|
|
2519
|
+
/** One type `contentTypes()` reports. */
|
|
2520
|
+
interface DeployedContentType {
|
|
2521
|
+
/** What `content()` or `singleton()` takes. */
|
|
2522
|
+
readonly id: string;
|
|
2523
|
+
/** The type's `displayName`. */
|
|
2524
|
+
readonly name: string;
|
|
2525
|
+
readonly type: 'content' | 'singleton';
|
|
2526
|
+
}
|
|
2527
|
+
/**
|
|
2528
|
+
* The defaults `client.with()` gives the client it makes (REL-2417, Q9): the locale and the dataset
|
|
2529
|
+
* its reads and writes are in unless a call names its own. A locale is one the schema declares.
|
|
2530
|
+
*/
|
|
2531
|
+
interface ClientDefaults<TSchema extends AnySchemaDefinition | undefined = undefined> {
|
|
2532
|
+
readonly locale?: LocaleCodeOf<TSchema>;
|
|
2533
|
+
readonly dataset?: string;
|
|
2534
|
+
}
|
|
2535
|
+
/** The locale codes a client's reads may name: the schema's, or any where no schema types it. */
|
|
2536
|
+
type LocaleCodeOf<TSchema extends AnySchemaDefinition | undefined> = TSchema extends AnySchemaDefinition ? SchemaLocaleCodes<TSchema> : string;
|
|
2537
|
+
/**
|
|
2538
|
+
* Client configuration with optional schema
|
|
2539
|
+
*/
|
|
2540
|
+
interface TypedClientConfig<TSchema extends AnySchemaDefinition | undefined = undefined> extends RelyIOConfig {
|
|
2541
|
+
/** Schema definition for type inference (optional - omit for dynamic/untyped usage) */
|
|
2542
|
+
schema?: TSchema;
|
|
2543
|
+
}
|
|
2544
|
+
/**
|
|
2545
|
+
* Typed RelyIO client with full schema awareness
|
|
2546
|
+
*
|
|
2547
|
+
* @example
|
|
2548
|
+
* ```typescript
|
|
2549
|
+
* import { createClient } from '@relyio/sdk'
|
|
2550
|
+
* import { schema } from './schema'
|
|
2551
|
+
*
|
|
2552
|
+
* const relyio = createClient({
|
|
2553
|
+
* apiKey: process.env.RELYIO_API_KEY!,
|
|
2554
|
+
* schema,
|
|
2555
|
+
* })
|
|
2556
|
+
*
|
|
2557
|
+
* // Content operations
|
|
2558
|
+
* const { data: products } = await relyio.content('product').list({ limit: 20 })
|
|
2559
|
+
*
|
|
2560
|
+
* // Search
|
|
2561
|
+
* const results = await relyio.search('wireless', { contentType: 'product' })
|
|
2562
|
+
*
|
|
2563
|
+
* // Batch operations
|
|
2564
|
+
* const batch = await relyio.batch([{ create: 'product', data: { title: 'New' } }])
|
|
2565
|
+
* ```
|
|
2566
|
+
*/
|
|
2567
|
+
declare class TypedRelyIOClient<TSchema extends AnySchemaDefinition | undefined = undefined> {
|
|
2568
|
+
/**
|
|
2569
|
+
* The one HTTP client every sub-client sends through. `updateConfig()` and `setApiKey()` change it
|
|
2570
|
+
* in place rather than replacing it, so a sub-client taken before either sends through the change.
|
|
2571
|
+
*/
|
|
2572
|
+
private httpClient;
|
|
2573
|
+
private config;
|
|
2574
|
+
/**
|
|
2575
|
+
* The configuration as `createClient()`, `setApiKey()` and `updateConfig()` gave it, before the
|
|
2576
|
+
* `relyio dev` handshake: what the next update is laid over, and what `with()` starts from.
|
|
2577
|
+
*/
|
|
2578
|
+
private configured;
|
|
2579
|
+
private _sandbox;
|
|
2580
|
+
private _schema;
|
|
2581
|
+
/**
|
|
2582
|
+
* The client's locale as it is when a call is made. Every sub-client and builder is handed this
|
|
2583
|
+
* rather than a copy of the locale, so one taken before `updateConfig({ locale })` sends the new
|
|
2584
|
+
* locale, or none after `locale: null` (REL-1453).
|
|
2585
|
+
*/
|
|
2586
|
+
private readonly currentLocale;
|
|
2587
|
+
/**
|
|
2588
|
+
* The client `with()` was called on, for a client it made: whose key, base URL and configuration
|
|
2589
|
+
* this one reads with as they are when each request is sent. `undefined` for a client
|
|
2590
|
+
* `createClient()` made, which reads its own.
|
|
2591
|
+
*/
|
|
2592
|
+
private parent;
|
|
2593
|
+
/** What `with()` gave this client over its parent's: the locale and dataset it names, and no other. */
|
|
2594
|
+
private defaults;
|
|
2595
|
+
/** The live edge's poll client id: chosen once, when first needed, and kept for this client's life. */
|
|
2596
|
+
private _liveClientId;
|
|
2597
|
+
constructor(config: TypedClientConfig<TSchema>);
|
|
2598
|
+
/**
|
|
2599
|
+
* The `relyio dev` sandbox this client resolved at construction, as the preview toolbar displays
|
|
2600
|
+
* it (`sdk-surfaces-spec.md` decision 22).
|
|
2601
|
+
*
|
|
2602
|
+
* Plain, JSON-serializable data and no key, so a server component can hand it to a client
|
|
2603
|
+
* component as a prop — the browser cannot read the handshake itself, so a client created there
|
|
2604
|
+
* reports `absent`. See {@link SandboxState} for what each state means.
|
|
2605
|
+
*
|
|
2606
|
+
* @example
|
|
2607
|
+
* ```tsx
|
|
2608
|
+
* // app/layout.tsx — a server component handing the state to a client component
|
|
2609
|
+
* <SandboxBadge sandbox={relyio.sandbox} />
|
|
2610
|
+
* ```
|
|
2611
|
+
*/
|
|
2612
|
+
get sandbox(): SandboxState;
|
|
2613
|
+
/**
|
|
2614
|
+
* Run a query in one call (REL-2421, REL-2409 Q22):
|
|
2615
|
+
*
|
|
2616
|
+
* - `query({ from, … })` runs one inline, with `list`'s option names — `where` (the query
|
|
2617
|
+
* route's dialect), `include`, `select`, `sort`, `limit`, `page`, `locale`, `dataset`, `fresh` —
|
|
2618
|
+
* plus `aggregate`, `params` and `fullText`;
|
|
2619
|
+
* - `query(name, { params, … })` runs the saved query the schema declares under `name`, its
|
|
2620
|
+
* `params` typed from that declaration.
|
|
2621
|
+
*
|
|
2622
|
+
* Both answer the API's body, `{ data: { result, metadata } }`. See {@link QueryCall}. Every query
|
|
2623
|
+
* is sent in the client's locale as it is then, unless it names its own.
|
|
2624
|
+
*/
|
|
2625
|
+
get query(): QueryCall<TSchema>;
|
|
2626
|
+
/**
|
|
2627
|
+
* Get a content client for a specific content type
|
|
2628
|
+
* Type-safe when schema is provided: autocompletes content type names AND field names
|
|
2629
|
+
*
|
|
2630
|
+
* @example
|
|
2631
|
+
* ```typescript
|
|
2632
|
+
* const { data: products } = await relyio.content('product').list({
|
|
2633
|
+
* where: { status: 'active' }, // ← Field names autocomplete with schema!
|
|
2634
|
+
* locale: 'en-US',
|
|
2635
|
+
* limit: 20,
|
|
2636
|
+
* })
|
|
2637
|
+
* ```
|
|
2638
|
+
*/
|
|
2639
|
+
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>;
|
|
2640
|
+
/**
|
|
2641
|
+
* Get an asset client for a specific asset type.
|
|
2642
|
+
*
|
|
2643
|
+
* Reads and writes as `content()` does — `list`, `get`, `listAll`, `update`, `delete` — over
|
|
2644
|
+
* `entryType: 'asset'` entries on the same dynamic `/{assetTypeId}` routes, and adds `create`,
|
|
2645
|
+
* the `file(entryUid)` sub-resource builder and a high-level `upload()` helper. Type-safe when a schema is
|
|
2646
|
+
* provided: autocompletes asset type names AND metadata field names.
|
|
2647
|
+
*
|
|
2648
|
+
* @example
|
|
2649
|
+
* ```typescript
|
|
2650
|
+
* const photo = await relyio.asset('productPhoto').upload(file)
|
|
2651
|
+
* .filename('shoe.jpg')
|
|
2652
|
+
* .visibility('public')
|
|
2653
|
+
* .execute()
|
|
2654
|
+
*
|
|
2655
|
+
* const heroUrl = await relyio.asset('productPhoto').file(photo._metadata.id).url({ variantId: 'hero' })
|
|
2656
|
+
* ```
|
|
2657
|
+
*/
|
|
2658
|
+
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>;
|
|
2659
|
+
/**
|
|
2660
|
+
* Get a singleton client for a specific singleton key
|
|
2661
|
+
* Type-safe when schema is provided: autocompletes singleton names AND field names
|
|
2662
|
+
*
|
|
2663
|
+
* @example
|
|
2664
|
+
* ```typescript
|
|
2665
|
+
* const homepage = await relyio.singleton('homepage').get({
|
|
2666
|
+
* include: ['featuredProducts'],
|
|
2667
|
+
* locale: 'en',
|
|
2668
|
+
* })
|
|
2669
|
+
* ```
|
|
2670
|
+
*/
|
|
2671
|
+
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<BaseSingleton>;
|
|
2672
|
+
/**
|
|
2673
|
+
* Get a settings client for a specific settings group
|
|
2674
|
+
* Type-safe when schema is provided: autocompletes settings group names AND field names
|
|
2675
|
+
*
|
|
2676
|
+
* @example
|
|
2677
|
+
* ```typescript
|
|
2678
|
+
* const emailConfig = await relyio.settings('email-config').get({
|
|
2679
|
+
* include: ['defaultTemplate'],
|
|
2680
|
+
* })
|
|
2681
|
+
* ```
|
|
2682
|
+
*/
|
|
2683
|
+
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<BaseSettingsGroup>;
|
|
2684
|
+
/**
|
|
2685
|
+
* Search, one page at a time: `search(q, options)` (REL-2420, REL-2409 Q19).
|
|
2686
|
+
*
|
|
2687
|
+
* Pages by number — `page` and `limit` — and answers the API's `{ data, page, meta }`, the page
|
|
2688
|
+
* `{ number, limit, total, pageCount }`: `total` counts every entry the search matched, so "page 2
|
|
2689
|
+
* of 3" needs no arithmetic. Where a schema types the client, `contentType`, `select`,
|
|
2690
|
+
* `searchFields`, `sort` and `locale` are checked against it and the hits are its entries — every
|
|
2691
|
+
* content type's when `contentType` is left out (`INV-SS-TYPES-INFERRED`).
|
|
2692
|
+
*
|
|
2693
|
+
* @example
|
|
2694
|
+
* ```typescript
|
|
2695
|
+
* const { data, page } = await relyio.search('wireless headphones', {
|
|
2696
|
+
* contentType: 'product',
|
|
2697
|
+
* where: { status: 'active' },
|
|
2698
|
+
* page: 2,
|
|
2699
|
+
* limit: 20,
|
|
2700
|
+
* })
|
|
2701
|
+
* console.log(`page ${page.number} of ${page.pageCount}`)
|
|
2702
|
+
* ```
|
|
2703
|
+
*/
|
|
2704
|
+
search<K extends SearchTypeName<TSchema> = SearchTypeName<TSchema>, const S extends readonly SearchFieldKey<SearchEntry<TSchema, K>>[] | undefined = undefined>(query: string, options: SearchOptions<K, SearchEntry<TSchema, K>, LocaleCodeOf<TSchema>, SearchWhere<TSchema, K>, S>): Promise<SearchPage<SearchSelected<SearchEntry<TSchema, K>, S>>>;
|
|
2705
|
+
/**
|
|
2706
|
+
* Suggestions completing `query` in one Field: `autocomplete(q, options)` (REL-2420).
|
|
2707
|
+
*
|
|
2708
|
+
* @example
|
|
2709
|
+
* ```typescript
|
|
2710
|
+
* const { data } = await relyio.autocomplete('wire', { contentType: 'product', field: 'title' })
|
|
2711
|
+
* ```
|
|
2712
|
+
*/
|
|
2713
|
+
autocomplete<K extends SearchTypeName<TSchema>>(query: string, options: AutocompleteOptions<K, SearchEntry<TSchema, K>, LocaleCodeOf<TSchema>, SearchWhere<TSchema, K>>): Promise<AutocompleteResponse>;
|
|
2714
|
+
/**
|
|
2715
|
+
* The entries most like the entry `sourceId`, by its stored vector: `similar(id, options)`
|
|
2716
|
+
* (REL-2420). Every content type's neighbours unless `contentType` names some.
|
|
2717
|
+
*
|
|
2718
|
+
* @example
|
|
2719
|
+
* ```typescript
|
|
2720
|
+
* const { data } = await relyio.similar('entry_abc123', { contentType: 'product', limit: 5 })
|
|
2721
|
+
* ```
|
|
2722
|
+
*/
|
|
2723
|
+
similar<K extends SearchTypeName<TSchema> = SearchTypeName<TSchema>, const S extends readonly SearchFieldKey<SearchEntry<TSchema, K>>[] | undefined = undefined>(sourceId: string, options: SimilarOptions<K, SearchEntry<TSchema, K>, LocaleCodeOf<TSchema>, S>): Promise<SimilarResponse<SearchSelected<SearchEntry<TSchema, K>, S>>>;
|
|
2724
|
+
/**
|
|
2725
|
+
* Write a list of operations in one request (REL-2421, REL-2409 Q22): each a create, update,
|
|
2726
|
+
* replace or delete of an entry, or a singleton write.
|
|
2727
|
+
*
|
|
2728
|
+
* An update, replace or delete takes the **entry you read** — checked against the version it
|
|
2729
|
+
* carries, in the locale it was read in, as `update(entry, data)` is — or an **id**, which writes
|
|
2730
|
+
* unchecked, on purpose. A stale entry fails its operation with `RESOURCE_CONFLICT` naming the
|
|
2731
|
+
* versions that disagreed: re-read it with `fresh: true` and retry. With `atomic: true` nothing
|
|
2732
|
+
* in the batch is written then — and the API takes an atomic batch only when every operation is
|
|
2733
|
+
* the same kind of write.
|
|
2734
|
+
*
|
|
2735
|
+
* Answers the API's body, `{ data: { results, summary } }`: one result per operation, in order,
|
|
2736
|
+
* each an `outcome` or an `error`. An operation that failed is not thrown.
|
|
2737
|
+
*
|
|
2738
|
+
* @example
|
|
2739
|
+
* ```typescript
|
|
2740
|
+
* const post = await relyio.content('post').get('hello-world')
|
|
2741
|
+
* const { data } = await relyio.batch([
|
|
2742
|
+
* { create: 'post', data: { title: 'New' } },
|
|
2743
|
+
* ...(post ? [{ update: 'post' as const, entry: post, data: { title: 'Changed' } }] : []),
|
|
2744
|
+
* { delete: 'post', id: 'entry_xyz' },
|
|
2745
|
+
* { singleton: 'homepage', data: { hero: 'Welcome!' } },
|
|
2746
|
+
* ])
|
|
2747
|
+
* console.log(data.summary.succeeded)
|
|
2748
|
+
* ```
|
|
2749
|
+
*/
|
|
2750
|
+
batch(operations: readonly BatchWrite<TSchema>[], options?: BatchWriteOptions): Promise<BatchBody>;
|
|
2751
|
+
/**
|
|
2752
|
+
* Follow the dataset's changes feed: each write this key can see, as ids, in
|
|
2753
|
+
* order, long-polled from `GET /v1/changes`.
|
|
2754
|
+
*
|
|
2755
|
+
* Lazy — nothing is requested until the first `next()`. It runs until
|
|
2756
|
+
* `signal` aborts, which ends the `for await` cleanly rather than throwing.
|
|
2757
|
+
* A change names what changed; refetch it through the ordinary read path
|
|
2758
|
+
* (`INV-SS-CHANGE-IS-IDS`), naming its `fresh` so no cache answers with the
|
|
2759
|
+
* body from before it (`FeedChange`).
|
|
2760
|
+
*
|
|
2761
|
+
* @example
|
|
2762
|
+
* ```typescript
|
|
2763
|
+
* const controller = new AbortController()
|
|
2764
|
+
* for await (const change of relyio.changes({ since: cursor, types: ['post'], signal: controller.signal })) {
|
|
2765
|
+
* // { entryId, contentType, locale, kind, cursor, fresh }
|
|
2766
|
+
* await relyio.content(change.contentType).get(change.entryId, { fresh: change.fresh })
|
|
2767
|
+
* }
|
|
2768
|
+
* ```
|
|
2769
|
+
*
|
|
2770
|
+
* @example Resync when the cursor has expired
|
|
2771
|
+
* ```typescript
|
|
2772
|
+
* for await (const change of relyio.changes({
|
|
2773
|
+
* since: cursor,
|
|
2774
|
+
* onExpired: async expired => refetchEverything(expired.fresh),
|
|
2775
|
+
* })) { … }
|
|
2776
|
+
* ```
|
|
2777
|
+
*/
|
|
2778
|
+
changes(options?: ChangesOptions): AsyncIterable<FeedChange>;
|
|
2779
|
+
/**
|
|
2780
|
+
* Follow the dataset's **published** changes at RelyIO's live edge, with a publishable key
|
|
2781
|
+
* (`rly_pub_…`): each change as ids, in order — the transport a browser follows changes with.
|
|
2782
|
+
*
|
|
2783
|
+
* A WebSocket to the edge, from `since` or the dataset's current position; where a socket cannot
|
|
2784
|
+
* open — the runtime has none, the network refuses it, or it has not opened and been told its
|
|
2785
|
+
* position within `LIVE_SOCKET_OPEN_TIMEOUT_MS` — it polls the edge instead, about every fifteen
|
|
2786
|
+
* seconds, from the same cursor. The edge is `liveUrl`, or derived from the default `baseUrl`
|
|
2787
|
+
* (see `RelyIOConfig.liveUrl`).
|
|
2788
|
+
*
|
|
2789
|
+
* Without `since` it starts at the dataset's current position, which it hands `reached` before
|
|
2790
|
+
* anything after it: read what you show after that call, and a publish your read did not see is
|
|
2791
|
+
* one this subscription announces (see `LiveChangesOptions.since`).
|
|
2792
|
+
*
|
|
2793
|
+
* One subscription ends only when `signal` aborts, which ends the `for await` cleanly; every other
|
|
2794
|
+
* end — a socket a deploy dropped, a refusal, a refused poll — is thrown as a `LiveEdgeError`
|
|
2795
|
+
* that says whether to stop, retry or back off. Hand it to `followChanges()`, the one loop that
|
|
2796
|
+
* reconnects from the last cursor with jittered back-off and honours those answers:
|
|
2797
|
+
*
|
|
2798
|
+
* @example
|
|
2799
|
+
* ```typescript
|
|
2800
|
+
* const relyio = createClient({ apiKey: 'rly_pub_…' })
|
|
2801
|
+
* const controller = new AbortController()
|
|
2802
|
+
* await followChanges(from => relyio.liveChanges({ ...from, types: ['post'] }), {
|
|
2803
|
+
* signal: controller.signal,
|
|
2804
|
+
* // `fresh` names a position no cache holds a body for from before the change.
|
|
2805
|
+
* onChange: change =>
|
|
2806
|
+
* relyio.content(change.contentType).get(change.entryId, { fresh: change.fresh }),
|
|
2807
|
+
* onResync: resync => refetchEverything(resync.cause === 'expired' ? resync.expired.fresh : undefined),
|
|
2808
|
+
* })
|
|
2809
|
+
* ```
|
|
2810
|
+
*
|
|
2811
|
+
* A client without a publishable key sends nothing: a secret or preview key is never put in the
|
|
2812
|
+
* edge's URL. On a server, follow `changes()` with it instead.
|
|
2813
|
+
*/
|
|
2814
|
+
liveChanges(options?: LiveChangesOptions): AsyncIterable<FeedChange>;
|
|
2815
|
+
/**
|
|
2816
|
+
* The dataset's current position in the changes feed, as a cursor to start
|
|
2817
|
+
* `changes({ since })` or `followChanges(…, { since })` from. One
|
|
2818
|
+
* `GET /v1/changes` without a cursor: answered at once, holding nothing.
|
|
2819
|
+
*
|
|
2820
|
+
* Take it **before** you read what you will follow. A write your read did not
|
|
2821
|
+
* see is then after the position and replayed; opening the feed afterwards
|
|
2822
|
+
* without a `since` starts after it and misses it.
|
|
2823
|
+
*
|
|
2824
|
+
* @example
|
|
2825
|
+
* ```typescript
|
|
2826
|
+
* const since = await relyio.changesHead()
|
|
2827
|
+
* const post = await relyio.content('post').get(id)
|
|
2828
|
+
* for await (const change of relyio.changes({ since, ids: [id] })) { … }
|
|
2829
|
+
* ```
|
|
2830
|
+
*/
|
|
2831
|
+
changesHead(options?: ChangesHeadOptions): Promise<string>;
|
|
2832
|
+
/**
|
|
2833
|
+
* Describe the project this key resolves to — `GET /v1/project`.
|
|
2834
|
+
*
|
|
2835
|
+
* `dataset` is the one this client's requests select (`dataset`, a sandbox from the `relyio dev`
|
|
2836
|
+
* handshake, or the key's default), which is not always the project's default, so a Studio link
|
|
2837
|
+
* built from `studioUrl` should carry it as `?dataset=`. `studioUrl` is `null` — a value, not an
|
|
2838
|
+
* error — when the service has no Dashboard address configured.
|
|
2839
|
+
*
|
|
2840
|
+
* @example
|
|
2841
|
+
* ```typescript
|
|
2842
|
+
* const { studioUrl, dataset } = await relyio.project()
|
|
2843
|
+
* if (studioUrl !== null) window.open(`${studioUrl}?dataset=${dataset.id}`)
|
|
2844
|
+
* ```
|
|
2845
|
+
*/
|
|
2846
|
+
project(): Promise<Project>;
|
|
2847
|
+
/**
|
|
2848
|
+
* The schema definition the key's branch has deployed, as stored — `GET /v1/meta/schema` — or,
|
|
2849
|
+
* given a content type or singleton id, that one type's definition — `GET /v1/meta/schema/{id}`.
|
|
2850
|
+
*
|
|
2851
|
+
* It answers what the deployed schema declares; `project()` answers where the key points
|
|
2852
|
+
* (organization, project, dataset, branch, Studio link). The two share no field.
|
|
2853
|
+
*
|
|
2854
|
+
* @example
|
|
2855
|
+
* ```typescript
|
|
2856
|
+
* const definition = await relyio.schema('post')
|
|
2857
|
+
* ```
|
|
2858
|
+
*/
|
|
2859
|
+
schema(contentType?: string): Promise<unknown>;
|
|
2860
|
+
/**
|
|
2861
|
+
* The content types and singletons the key's branch has deployed — `GET /v1/meta/content-types`.
|
|
2862
|
+
*
|
|
2863
|
+
* Each is named by its `id` (what `content()` and `singleton()` take), its `name` (the schema's
|
|
2864
|
+
* `displayName`) and its `type`, content types first, each group in the order the schema declares it.
|
|
2865
|
+
*
|
|
2866
|
+
* @example
|
|
2867
|
+
* ```typescript
|
|
2868
|
+
* const types = await relyio.contentTypes()
|
|
2869
|
+
* const singletons = types.filter(type => type.type === 'singleton').map(type => type.id)
|
|
2870
|
+
* ```
|
|
2871
|
+
*/
|
|
2872
|
+
contentTypes(): Promise<DeployedContentType[]>;
|
|
2873
|
+
/**
|
|
2874
|
+
* Whether the API can serve requests now — `GET /health/ready`.
|
|
2875
|
+
*
|
|
2876
|
+
* Resolves `{ status: 'ok', timestamp }` when the API answers and reaches every store it reads.
|
|
2877
|
+
* Rejects otherwise, with the error the request met: a `503` naming the store that did not answer,
|
|
2878
|
+
* or a network error when the API could not be reached at all. So there is no `status` to check:
|
|
2879
|
+
* a call that resolves is the healthy answer.
|
|
2880
|
+
*
|
|
2881
|
+
* @example
|
|
2882
|
+
* ```typescript
|
|
2883
|
+
* try {
|
|
2884
|
+
* await relyio.health()
|
|
2885
|
+
* } catch (error) {
|
|
2886
|
+
* console.error('RelyIO cannot serve now', error)
|
|
2887
|
+
* }
|
|
2888
|
+
* ```
|
|
2889
|
+
*/
|
|
2890
|
+
health(): Promise<Health>;
|
|
2891
|
+
/**
|
|
2892
|
+
* Change some of the client's options and keep the rest.
|
|
2893
|
+
*
|
|
2894
|
+
* What you pass is laid over the configuration the client holds, field by field:
|
|
2895
|
+
*
|
|
2896
|
+
* - an option you name replaces the value the client holds;
|
|
2897
|
+
* - an option left out, or given as `undefined`, keeps the value the client holds;
|
|
2898
|
+
* - an option with no default given as `null` is cleared — `locale`, `dataset`, `apiKey`, `dev`,
|
|
2899
|
+
* `fetch`, `browser.onCorsError`, `realtime.wsUrl`, `debug.logger` — so the client runs as though
|
|
2900
|
+
* it had never been given one. An option with a default takes no `null`;
|
|
2901
|
+
* - a bag of options you name — `browser`, `cache`, `realtime`, `debug` — keeps every field of it
|
|
2902
|
+
* you do not name, so `updateConfig({ browser: { includeCredentials: true } })` keeps a
|
|
2903
|
+
* configured `onCorsError`;
|
|
2904
|
+
* - `headers` is laid over the headers the client sends, a name matched without regard to case: a
|
|
2905
|
+
* header you name is added or replaces every spelling of it, every other one is still sent, and a
|
|
2906
|
+
* header given as `null` is no longer sent;
|
|
2907
|
+
* - naming `apiKey` — a key, or `null` — replaces every key the client holds, one given in
|
|
2908
|
+
* `headers` included.
|
|
2909
|
+
*
|
|
2910
|
+
* The configuration it is laid over is what `createClient()`, `setApiKey()` and earlier updates
|
|
2911
|
+
* gave the client. The `relyio dev` handshake is read again, as `createClient()` reads it: while a
|
|
2912
|
+
* sandbox is active its key and dataset are sent over the configured ones, and once it has gone
|
|
2913
|
+
* the configured ones are.
|
|
2914
|
+
*
|
|
2915
|
+
* The client's one HTTP client is changed in place, so every request from now on is sent as the
|
|
2916
|
+
* update says — including one from a `content()`, `asset()`, `singleton()` or `settings()`
|
|
2917
|
+
* sub-client taken before the call. The locale reaches them too: each reads the client's locale
|
|
2918
|
+
* when it sends, so a new `locale` is the default of its next request and `locale: null` leaves
|
|
2919
|
+
* that request with none. A locale the call names still wins over it, and so does the locale of a
|
|
2920
|
+
* client `with({ locale })` made from this one.
|
|
2921
|
+
*
|
|
2922
|
+
* @example
|
|
2923
|
+
* ```typescript
|
|
2924
|
+
* const relyio = createClient({ apiKey, headers: { 'X-Tenant': 'acme' }, browser: { onCorsError } })
|
|
2925
|
+
*
|
|
2926
|
+
* relyio.updateConfig({ headers: { 'X-Trace': traceId } }) // X-Tenant is still sent
|
|
2927
|
+
* relyio.updateConfig({ browser: { includeCredentials: true } }) // onCorsError is kept
|
|
2928
|
+
* relyio.updateConfig({ headers: { 'X-Tenant': null } }) // X-Tenant is no longer sent
|
|
2929
|
+
* relyio.updateConfig({ locale: null }) // reads name no locale of the client's
|
|
2930
|
+
* ```
|
|
2931
|
+
*/
|
|
2932
|
+
updateConfig(update: RelyIOConfigUpdate): void;
|
|
2933
|
+
/**
|
|
2934
|
+
* Get the current configuration
|
|
2935
|
+
*/
|
|
2936
|
+
getConfig(): Readonly<MergedRelyIOConfig>;
|
|
2937
|
+
/**
|
|
2938
|
+
* Replace the API key every later request sends as `X-API-Key`.
|
|
2939
|
+
*
|
|
2940
|
+
* It reaches every request this client makes from now on, including those of a `content()`,
|
|
2941
|
+
* `asset()` or other sub-client taken before the call, and `getConfig().apiKey` reports it. A key
|
|
2942
|
+
* given in `headers`, under any spelling, is replaced with it and does not come back on a later
|
|
2943
|
+
* `updateConfig()`. A client `with()` made from this one sends the new key too: the two share one
|
|
2944
|
+
* connection.
|
|
2945
|
+
*
|
|
2946
|
+
* An empty or blank key is no key, as it is when passed to `createClient`: the next request
|
|
2947
|
+
* rejects with `MissingApiKeyError` and nothing is sent.
|
|
2948
|
+
*
|
|
2949
|
+
* Under an active `relyio dev` sandbox the sandbox's scoped key stands, as it does over the key
|
|
2950
|
+
* passed to `createClient` — replacing the configured key does not retarget the client at the
|
|
2951
|
+
* sandbox's dataset with a key the sandbox did not issue.
|
|
2952
|
+
*
|
|
2953
|
+
* @example
|
|
2954
|
+
* ```typescript
|
|
2955
|
+
* const relyio = createClient({ apiKey: process.env.RELYIO_API_KEY! })
|
|
2956
|
+
* relyio.setApiKey(rotatedKey) // the next read sends `X-API-Key: <rotatedKey>`
|
|
2957
|
+
* ```
|
|
2958
|
+
*/
|
|
2959
|
+
setApiKey(apiKey: string): void;
|
|
2960
|
+
/**
|
|
2961
|
+
* Get the HTTP client instance (for advanced usage)
|
|
2962
|
+
*/
|
|
2963
|
+
getHttpClient(): HttpClient;
|
|
2964
|
+
/**
|
|
2965
|
+
* Get the current default locale
|
|
2966
|
+
*/
|
|
2967
|
+
getLocale(): string | undefined;
|
|
2968
|
+
/**
|
|
2969
|
+
* A client whose reads and writes are in `locale` and `dataset` unless a call names its own, and
|
|
2970
|
+
* which otherwise **follows this one** (REL-2417, Q9): it sends this client's key, to this
|
|
2971
|
+
* client's base URL, under this client's configuration as it is when each request is sent — so a
|
|
2972
|
+
* later `setApiKey()` or `updateConfig()` here reaches it. A default it does not name is this
|
|
2973
|
+
* client's, as this client changes it.
|
|
2974
|
+
*
|
|
2975
|
+
* The two share one connection, so a `setApiKey()` or `updateConfig()` called on the client made
|
|
2976
|
+
* here changes this one too. A call's own `locale` or `dataset` wins over the defaults.
|
|
2977
|
+
*
|
|
2978
|
+
* @example
|
|
2979
|
+
* ```typescript
|
|
2980
|
+
* const staging = relyio.with({ dataset: 'staging' })
|
|
2981
|
+
* const { data } = await staging.content('post').list({ limit: 10 })
|
|
2982
|
+
*
|
|
2983
|
+
* const french = relyio.with({ locale: 'fr-FR' }) // ← autocompletes the schema's locales
|
|
2984
|
+
* ```
|
|
2985
|
+
*/
|
|
2986
|
+
with(defaults: ClientDefaults<TSchema>): TypedRelyIOClient<TSchema>;
|
|
2987
|
+
/** Make this client one `with()` made from `parent`: see {@link with}. */
|
|
2988
|
+
private follow;
|
|
2989
|
+
/**
|
|
2990
|
+
* The configuration as given, before the `relyio dev` handshake — this client's own, or for a
|
|
2991
|
+
* client `with()` made, its parent's with its defaults laid over it.
|
|
2992
|
+
*/
|
|
2993
|
+
private configuredNow;
|
|
2994
|
+
/**
|
|
2995
|
+
* Every locale the key's branch has deployed, the default marked — `GET /v1/meta/locales`.
|
|
2996
|
+
*
|
|
2997
|
+
* In the order the schema declares them. Exactly one has `default: true`, the locale a read or
|
|
2998
|
+
* write that names none is in; every other has `default: false`. `fallback`, `direction` and
|
|
2999
|
+
* `active` are as the schema declares them, absent where it does not.
|
|
3000
|
+
*
|
|
3001
|
+
* @example
|
|
3002
|
+
* ```typescript
|
|
3003
|
+
* const locales = await relyio.locales()
|
|
3004
|
+
* const defaultLocale = locales.find(locale => locale.default)?.code
|
|
3005
|
+
*
|
|
3006
|
+
* // Build a language switcher
|
|
3007
|
+
* const options = locales.map(locale => ({ code: locale.code, label: locale.displayName }))
|
|
3008
|
+
* ```
|
|
3009
|
+
*/
|
|
3010
|
+
locales(): Promise<DeployedLocale[]>;
|
|
3011
|
+
}
|
|
3012
|
+
/**
|
|
3013
|
+
* Create a RelyIO client
|
|
3014
|
+
*
|
|
3015
|
+
* @example Without schema (dynamic usage)
|
|
3016
|
+
* ```typescript
|
|
3017
|
+
* import { createClient } from '@relyio/sdk'
|
|
3018
|
+
*
|
|
3019
|
+
* const relyio = createClient({
|
|
3020
|
+
* apiKey: process.env.RELYIO_API_KEY!,
|
|
3021
|
+
* })
|
|
3022
|
+
*
|
|
3023
|
+
* // Works but no autocomplete for content types
|
|
3024
|
+
* const { data: products } = await relyio.content('product').list()
|
|
3025
|
+
* ```
|
|
3026
|
+
*
|
|
3027
|
+
* @example With schema (fully typed)
|
|
3028
|
+
* ```typescript
|
|
3029
|
+
* import { createClient } from '@relyio/sdk'
|
|
3030
|
+
* import { schema } from './schema'
|
|
3031
|
+
*
|
|
3032
|
+
* const relyio = createClient({
|
|
3033
|
+
* apiKey: process.env.RELYIO_API_KEY!,
|
|
3034
|
+
* schema,
|
|
3035
|
+
* })
|
|
3036
|
+
*
|
|
3037
|
+
* // Content type names autocomplete!
|
|
3038
|
+
* const { data: products } = await relyio.content('product').list()
|
|
3039
|
+
* ```
|
|
3040
|
+
*
|
|
3041
|
+
* @example After `relyio generate-types` (inferred, `INV-SS-TYPES-INFERRED`)
|
|
3042
|
+
* ```typescript
|
|
3043
|
+
* import { createClient } from '@relyio/sdk'
|
|
3044
|
+
*
|
|
3045
|
+
* // The generated `schema.ts` augments `Register`, so no generic and no `schema:` are needed
|
|
3046
|
+
* const relyio = createClient({ apiKey: process.env.RELYIO_API_KEY! })
|
|
3047
|
+
* ```
|
|
3048
|
+
*
|
|
3049
|
+
* The overload without a schema comes first on purpose. Behind the generic one it was never
|
|
3050
|
+
* chosen: with no `schema` to infer from, `TSchema` fell back to its constraint and the call
|
|
3051
|
+
* resolved to `TypedRelyIOClient<AnySchemaDefinition>`. First, it takes every call that passes no
|
|
3052
|
+
* schema, and returns the registered schema's client — or the untyped one when nothing registered.
|
|
3053
|
+
* A `schema:` value fails its `schema?: undefined`, and an explicit generic skips it because it has
|
|
3054
|
+
* no type parameter, so both reach the generic overload unchanged.
|
|
3055
|
+
*/
|
|
3056
|
+
declare function createClient(config: RelyIOConfig & {
|
|
3057
|
+
schema?: undefined;
|
|
3058
|
+
}): TypedRelyIOClient<RegisteredSchema>;
|
|
3059
|
+
declare function createClient<TSchema extends AnySchemaDefinition>(config: TypedClientConfig<TSchema>): TypedRelyIOClient<TSchema>;
|
|
3060
|
+
/**
|
|
3061
|
+
* Helper type to extract schema from client
|
|
3062
|
+
*/
|
|
3063
|
+
type ClientSchema<T extends TypedRelyIOClient<AnySchemaDefinition>> = T extends TypedRelyIOClient<infer TSchema> ? TSchema : never;
|
|
3064
|
+
|
|
3065
|
+
export { type ListOptions as $, type AnySchemaDefinition as A, type EntryWriteOptions as B, type Change as C, type DeployedLocale as D, type EntryPlacement as E, type FeedChange as F, SingletonClient as G, type Health as H, SettingsClient as I, type DocumentFields as J, type DocumentVersion as K, type LocaleCodeOf as L, type MatchRouteOptions as M, type DocumentVersionsOptions as N, type DocumentWriteOptions as O, type Project as P, type ChildrenOptions as Q, type Register as R, type SandboxState as S, TypedRelyIOClient as T, type DocumentReadOptions as U, type EntryList as V, type Wire as W, type FieldKey as X, type FreshOption as Y, type GetOptions as Z, type ListAllOptions as _, ChangesExpiredError as a, type PresignInput as a$, type LocaleOption as a0, type Paging as a1, type ReadAs as a2, type ReadLocale as a3, type SortOption as a4, type VersionOptions as a5, type WrittenReadScope as a6, type WrittenSort as a7, type SearchResultItem as a8, type SearchHitMetadata as a9, type BatchReplace as aA, type BatchDelete as aB, type BatchSingletonWrite as aC, type BatchTarget as aD, type BatchDeleteTarget as aE, type AggregateMetric as aF, type QueryMetadata as aG, type AdhocQuery as aH, type AdhocRecord as aI, type NamedQueryOptions as aJ, type NamedQueryParams as aK, type QueryAggregate as aL, type QueryAnswer as aM, type QueryCall as aN, type QueryName as aO, type QueryTypeName as aP, AssetClient as aQ, AssetFileBuilder as aR, AssetUploadBuilder as aS, isAssetEntry as aT, type AssetVisibility as aU, type FileStatus as aV, type FileBackend as aW, type AssetFileExtracted as aX, type FileStatusResult as aY, type AssetEntryResponse as aZ, type PopulatedAssetReference as a_, type SearchResponse as aa, type AutocompleteSuggestion as ab, type AutocompleteResponse as ac, type SimilarResult as ad, type SimilarResponse as ae, type SearchTypeName as af, type SearchEntry as ag, type AutocompleteOptions as ah, type SearchFacet as ai, type SearchFieldKey as aj, type SearchHighlight as ak, type SearchOptions as al, type SearchPage as am, type SearchScope as an, type SearchSelected as ao, type SearchSortOption as ap, type SearchWhere as aq, type SimilarOptions as ar, type BatchOperationResult as as, type BatchSummary as at, type BatchResponse as au, type BatchWrite as av, type BatchWriteOptions as aw, type BatchBody as ax, type BatchCreate as ay, type BatchUpdate as az, type ChangesExpiredReason as b, type PresignResult as b0, type ConfirmInput as b1, type ConfirmResult as b2, type ResolveUrlInput as b3, type ResolveUrlResult as b4, type CreateAssetOptions as b5, type AssetPlacement as b6, type UpdateAssetOptions as b7, type WaitUntilReadyOptions as b8, type MultipartCreateResult as b9, type MultipartPart as ba, type MultipartTarget as bb, type UploadProgress as bc, ResourceNotFoundError as bd, ResourceConflictError as be, ResourceExpiredError as bf, isResourceNotFound as bg, isMissingEntry as bh, type FileErrorCode as bi, type FileUploadErrorCode as bj, FileUploadError as bk, FileValidationError as bl, type FileValidationConstraints as bm, validateFile as bn, createClient as c, type TypedClientConfig as d, type ClientDefaults as e, type ClientSchema as f, type DeployedContentType as g, type RegisteredSchema as h, type ActiveSandbox as i, HttpClient as j, type SendOptions as k, type ApiPath as l, type ApiMethod as m, type ApiSuccessBody as n, matchRoute as o, type RouteMatch as p, type ReadEntry as q, type RetryableWriteOptions as r, type ChangesHeadOptions as s, type ChangesOptions as t, LIVE_KEEPALIVE_INTERVAL_MS as u, versionCheckOf as v, LIVE_SOCKET_OPEN_TIMEOUT_MS as w, type LiveChangesOptions as x, ContentClient as y, type CreateEntryOptions as z };
|