@relyio/sdk 0.19.0 → 0.20.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 +10 -0
- package/dist/automations/index.cjs +1 -1
- package/dist/automations/index.d.cts +2 -1588
- package/dist/automations/index.d.ts +2 -1588
- package/dist/automations/index.js +1 -1
- package/dist/{chunk-ZO6QDYJG.cjs → chunk-5WGMIQBH.cjs} +2 -2
- package/dist/{chunk-W2A4OTCI.js → chunk-EQUZNVAR.js} +2 -2
- package/dist/{chunk-P23G5NXD.cjs → chunk-K66MBRI4.cjs} +68 -51
- package/dist/{chunk-356SF5Y2.js → chunk-L42C4G2S.js} +25 -8
- package/dist/{chunk-T7LGREB3.js → chunk-OUR7OUUZ.js} +3 -3
- package/dist/{chunk-NN45CSPZ.cjs → chunk-UO2QRYKV.cjs} +3 -3
- package/dist/client/index.cjs +5 -5
- package/dist/client/index.d.cts +4 -4
- package/dist/client/index.d.ts +4 -4
- package/dist/client/index.js +3 -3
- package/dist/{define-content-type-BZ6X8GvL.d.cts → define-content-type-C2HnCYYX.d.cts} +1 -1
- package/dist/{define-content-type-C12jh8cY.d.ts → define-content-type-CVLUusfx.d.ts} +1 -1
- package/dist/dev-handshake/index.cjs +8 -8
- package/dist/dev-handshake/index.d.cts +1 -1
- package/dist/dev-handshake/index.d.ts +1 -1
- package/dist/dev-handshake/index.js +1 -1
- package/dist/extensions/index.cjs +3 -3
- package/dist/extensions/index.d.cts +2 -4
- package/dist/extensions/index.d.ts +2 -4
- package/dist/extensions/index.js +1 -1
- package/dist/functions/index.cjs +1 -1
- package/dist/functions/index.d.cts +2 -4
- package/dist/functions/index.d.ts +2 -4
- package/dist/functions/index.js +1 -1
- package/dist/{index-D5jyIeQ5.d.cts → index-BLVNMYUU.d.cts} +54 -9
- package/dist/{index-C-MymZTe.d.ts → index-BY1xU93u.d.ts} +1 -1
- package/dist/{index-DN0l8RHw.d.cts → index-Cmup6HJY.d.cts} +1 -1
- package/dist/index-D1fiaQlK.d.ts +2284 -0
- package/dist/index-DImzg3ew.d.cts +2284 -0
- package/dist/{index-BxNnlc5D.d.ts → index-IjSyBr8f.d.ts} +54 -9
- package/dist/index.cjs +76 -76
- package/dist/index.d.cts +7 -8
- package/dist/index.d.ts +7 -8
- package/dist/index.js +4 -4
- package/dist/preview/index.cjs +3 -3
- package/dist/preview/index.js +1 -1
- package/dist/rich-text/index.d.cts +2 -2
- package/dist/rich-text/index.d.ts +2 -2
- package/dist/schema/index.cjs +1 -1
- package/dist/schema/index.d.cts +947 -4
- package/dist/schema/index.d.ts +947 -4
- package/dist/schema/index.js +1 -1
- package/dist/types/index.d.cts +1 -1
- package/dist/types/index.d.ts +1 -1
- package/package.json +4 -4
- package/dist/define-schema-BPlZl4a2.d.cts +0 -947
- package/dist/define-schema-CK7TtYot.d.ts +0 -947
- package/dist/register-BiMQKVp-.d.ts +0 -693
- package/dist/register-DeMsaurp.d.cts +0 -693
|
@@ -0,0 +1,2284 @@
|
|
|
1
|
+
import { cW as paths, cX as RetryConfig, cY as MergedRelyIOConfig, cZ as ApiResponse, ap as components, ar as FileUpload, cS as ConditionScalar, cT as ConditionTest, C as ConditionTree, cU as ConditionTests, s as KeyedMembers, an as FieldValues, ao as DeclaredFields, t as KeyedMembersOf, c_ as ExtensionSurfaces, c$ as ExtensionSettingField, d0 as ExtensionSurface, d1 as ActionField, d2 as ExtensionValueField, z as ReferenceConfig, p as KeyedFieldOptions, o as KeyedField } from './define-content-type-CVLUusfx.js';
|
|
2
|
+
import { SchemaDefinition, SchemaRecordTypeNames, InferRecordType, SchemaContentTypeNames, SchemaEntry, SchemaLocaleCodes, RecordWriteFieldsOf, RecordUpdateFieldsOf } from './schema/index.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Where the SDK's transport gets its truth.
|
|
6
|
+
*
|
|
7
|
+
* Every route this client calls is named by its **path template as the
|
|
8
|
+
* committed document spells it**, and the body type it expects is read out of
|
|
9
|
+
* that document rather than written down here. `api-schema.ts` beside this file
|
|
10
|
+
* is generated by `pnpm api:types` from `apps/services/api/openapi-spec.json`,
|
|
11
|
+
* which is itself generated from that service's `RouteContract`s
|
|
12
|
+
* (`INV-API-DOCUMENT-IS-DERIVED`). So the chain is: one declaration in
|
|
13
|
+
* `routes.ts` → the serializer that produces the body → the document → these
|
|
14
|
+
* types → the method that reads it.
|
|
15
|
+
*
|
|
16
|
+
* That chain is the answer to the question this phase exists to close. Before
|
|
17
|
+
* it, six SDK methods read a key the server does not send; each had no caller
|
|
18
|
+
* and no test, so nothing could see them. Now:
|
|
19
|
+
*
|
|
20
|
+
* - A route that changes **kind** — `collection` to `object`, say — changes
|
|
21
|
+
* whether the document's 200 body carries `page` and whether `data` is an
|
|
22
|
+
* array. `Wire<…>` follows, `readPayload` follows, and the method's declared
|
|
23
|
+
* return type stops matching. `tsc` names the method.
|
|
24
|
+
* - A route that changes **path** or is deleted stops being a `keyof paths`, so
|
|
25
|
+
* the call site fails on the literal.
|
|
26
|
+
* - A route that changes **dialect** changes `page` from `CursorPage` to
|
|
27
|
+
* `OffsetPage`, and anything reading `nextCursor` fails.
|
|
28
|
+
*
|
|
29
|
+
* `/v1` lives in these templates because it lives in the document
|
|
30
|
+
* (`INV-API-VERSION-IS-DECIDED-ONCE`). Before this phase the SDK's base URL was
|
|
31
|
+
* `https://api.relyio.com` and its paths were `/{contentType}`, so every
|
|
32
|
+
* versioned request went to a path the router does not serve.
|
|
33
|
+
*
|
|
34
|
+
* Spec: `devtools/docs/guide/engineering/api-contract-spec.md`,
|
|
35
|
+
* `INV-API-CLIENT-IS-DERIVED`.
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
/** Every path the committed document declares. */
|
|
39
|
+
type ApiPath = keyof paths;
|
|
40
|
+
type Verb = 'get' | 'post' | 'put' | 'patch' | 'delete';
|
|
41
|
+
/** The verbs the document declares on this path — not the seven HTTP has. */
|
|
42
|
+
type ApiMethod<P extends ApiPath> = {
|
|
43
|
+
[M in Verb]: M extends keyof paths[P] ? paths[P][M] extends {
|
|
44
|
+
responses: unknown;
|
|
45
|
+
} ? M : never : never;
|
|
46
|
+
}[Verb];
|
|
47
|
+
type Operation<P extends ApiPath, M extends ApiMethod<P>> = M extends keyof paths[P] ? paths[P][M] : never;
|
|
48
|
+
type JsonBody<R> = R extends {
|
|
49
|
+
content: {
|
|
50
|
+
'application/json': infer B;
|
|
51
|
+
};
|
|
52
|
+
} ? B : never;
|
|
53
|
+
/**
|
|
54
|
+
* The success body the document declares, preferring 200 where a route serves
|
|
55
|
+
* both. A route with neither — `kind: 'empty'`, which serves 204 — resolves to
|
|
56
|
+
* `never`, and `readPayload` cannot be called on it.
|
|
57
|
+
*/
|
|
58
|
+
type ApiSuccessBody<P extends ApiPath, M extends ApiMethod<P>> = Operation<P, M> extends {
|
|
59
|
+
responses: infer R;
|
|
60
|
+
} ? 200 extends keyof R ? JsonBody<R[200]> : 201 extends keyof R ? JsonBody<R[201]> : never : never;
|
|
61
|
+
/**
|
|
62
|
+
* The document's success body with the payload typed as the caller's `T`.
|
|
63
|
+
*
|
|
64
|
+
* The array arm is tested **first** on purpose: `{ data: unknown[] }` also
|
|
65
|
+
* satisfies `{ data: unknown }`, so the object arm would swallow every
|
|
66
|
+
* collection and hand a list route's caller a single entity — which is the
|
|
67
|
+
* exact defect class (`ContentClient.children` reading `.data.items` from an
|
|
68
|
+
* object route) that this phase makes unrepresentable.
|
|
69
|
+
*/
|
|
70
|
+
type Wire<P extends ApiPath, M extends ApiMethod<P>, T> = ApiSuccessBody<P, M> extends {
|
|
71
|
+
data: unknown[];
|
|
72
|
+
} ? Omit<ApiSuccessBody<P, M>, 'data'> & {
|
|
73
|
+
data: T[];
|
|
74
|
+
} : ApiSuccessBody<P, M> extends {
|
|
75
|
+
data: unknown;
|
|
76
|
+
} ? Omit<ApiSuccessBody<P, M>, 'data'> & {
|
|
77
|
+
data: T;
|
|
78
|
+
} : ApiSuccessBody<P, M>;
|
|
79
|
+
/**
|
|
80
|
+
* The path parameters the document declares for this operation, or `never`
|
|
81
|
+
* where it declares none.
|
|
82
|
+
*
|
|
83
|
+
* `never` rather than `{}`: a route with no segments must **forbid** a `path`
|
|
84
|
+
* argument, not merely tolerate an empty one, and `keyof Record<string, never>`
|
|
85
|
+
* is `string`, so an empty-object encoding cannot be told apart from a real
|
|
86
|
+
* one at the type level.
|
|
87
|
+
*/
|
|
88
|
+
type PathParamsOf<P extends ApiPath, M extends ApiMethod<P>> = Operation<P, M> extends {
|
|
89
|
+
parameters: {
|
|
90
|
+
path?: infer X;
|
|
91
|
+
};
|
|
92
|
+
} ? [X] extends [undefined] ? never : NonNullable<X> : never;
|
|
93
|
+
/**
|
|
94
|
+
* The query parameters the document declares for this operation, or `never` where it declares
|
|
95
|
+
* none (REL-687).
|
|
96
|
+
*
|
|
97
|
+
* The service declares them beside each route — a query DTO's whitelisted properties, the names a
|
|
98
|
+
* handler reads, `?_fresh` on a read the edge keeps — and `apps/services/api`'s
|
|
99
|
+
* `contract/query-parameters.spec.ts` holds the document to what each handler reads. So a name the
|
|
100
|
+
* API stops reading stops compiling here, and a name it refuses — `offset` on the entry list, which
|
|
101
|
+
* the SDK once sent on every `.offset()` and the API answered with a 400 — never compiled at all.
|
|
102
|
+
*
|
|
103
|
+
* `never` for the same reason {@link PathParamsOf} uses it: a route with no query must forbid one.
|
|
104
|
+
*/
|
|
105
|
+
type QueryOf<P extends ApiPath, M extends ApiMethod<P>> = Operation<P, M> extends {
|
|
106
|
+
parameters: {
|
|
107
|
+
query?: infer Q;
|
|
108
|
+
};
|
|
109
|
+
} ? [Q] extends [undefined] ? never : NonNullable<Q> : never;
|
|
110
|
+
/** What {@link matchRoute} recovers from a URL the client emitted. */
|
|
111
|
+
interface RouteMatch {
|
|
112
|
+
/** The document's path template — the value a tag translator switches on. */
|
|
113
|
+
path: ApiPath;
|
|
114
|
+
/** The template's `{name}` segments, decoded: the inverse of {@link fillPath}. */
|
|
115
|
+
params: Record<string, string>;
|
|
116
|
+
/** The query string; a key that appears more than once comes back as an array. */
|
|
117
|
+
query: Record<string, string | string[]>;
|
|
118
|
+
}
|
|
119
|
+
interface MatchRouteOptions {
|
|
120
|
+
/**
|
|
121
|
+
* The client's `baseUrl`, when it carries a path prefix of its own
|
|
122
|
+
* (`https://example.com/relyio`). Its path is removed before the template is
|
|
123
|
+
* matched. Unneeded for the default `https://api.relyio.com`.
|
|
124
|
+
*/
|
|
125
|
+
baseUrl?: string;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Recover the route a URL names: the inverse of {@link fillPath}, over the same
|
|
129
|
+
* committed document (`INV-API-CLIENT-IS-DERIVED`).
|
|
130
|
+
*
|
|
131
|
+
* `matchRoute(url).path` is an `ApiPath`, so anything that keys behaviour on a
|
|
132
|
+
* request — a cache-tag translator, say — switches on the document's templates
|
|
133
|
+
* and never on a route table of its own.
|
|
134
|
+
*
|
|
135
|
+
* Returns `null` for a URL no declared template matches — a presigned storage
|
|
136
|
+
* URL, or a path outside `baseUrl` — rather than a guess.
|
|
137
|
+
*/
|
|
138
|
+
declare function matchRoute(url: string | URL, options?: MatchRouteOptions): RouteMatch | null;
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* The part of a request a retry decision is allowed to look at.
|
|
142
|
+
*
|
|
143
|
+
* `method` is typed as a plain `string` rather than as `RequestConfig['method']`
|
|
144
|
+
* on purpose. The five verbs that union names are the five the typed transport
|
|
145
|
+
* can express today; the question "may this be sent twice?" is a fact about
|
|
146
|
+
* HTTP, and an unrecognised verb has to reach the conservative arm rather than
|
|
147
|
+
* be unrepresentable.
|
|
148
|
+
*/
|
|
149
|
+
interface RetryableRequest {
|
|
150
|
+
method: string;
|
|
151
|
+
headers?: Record<string, string>;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Whether a failed request may be sent again, and after how long.
|
|
155
|
+
*
|
|
156
|
+
* The two halves are separate because they fail separately. The incumbent loop
|
|
157
|
+
* had neither: it asked `error.retryable` and nothing else, so a `POST` that met
|
|
158
|
+
* a 500 was sent again twice — and `SERVICE_INDETERMINATE`, whose whole meaning
|
|
159
|
+
* is "the service could not decide", is marked retryable, which means the write
|
|
160
|
+
* may already have landed. Meanwhile the `Retry-After` the service sent was
|
|
161
|
+
* collected by the transport and read by nobody, so a 429 whose window is
|
|
162
|
+
* measured in minutes was retried after an exponential guess capped at ten
|
|
163
|
+
* seconds.
|
|
164
|
+
*
|
|
165
|
+
* `error` is `Error`, not `SDKError`, because that is what the loop actually
|
|
166
|
+
* holds: a `TypeError: fetch failed` is only mapped to an `SDKError` **after**
|
|
167
|
+
* the loop gives up, so a policy typed to `SDKError` could not see the transport
|
|
168
|
+
* failures it most needs to retry.
|
|
169
|
+
*/
|
|
170
|
+
interface RetryPolicy {
|
|
171
|
+
/**
|
|
172
|
+
* Never replays a request that could apply twice: a write is retried only
|
|
173
|
+
* when it carries `Idempotency-Key`, or the verb is idempotent.
|
|
174
|
+
*
|
|
175
|
+
* @param attempt - How many attempts have been made, 1 for the first.
|
|
176
|
+
*/
|
|
177
|
+
shouldRetry(attempt: number, error: Error, request: RetryableRequest): boolean;
|
|
178
|
+
/** Honours `Retry-After` / `RateLimitError.resetAt` over exponential backoff. */
|
|
179
|
+
delayFor(attempt: number, error: Error): number;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Whether sending this request a second time can apply it a second time.
|
|
183
|
+
*
|
|
184
|
+
* Two ways to be safe, and they are genuinely different questions: the verb may
|
|
185
|
+
* be idempotent by definition, or the request may carry an `Idempotency-Key`
|
|
186
|
+
* that makes the service answer a replay from the original outcome rather than
|
|
187
|
+
* applying it again.
|
|
188
|
+
*/
|
|
189
|
+
declare function isReplaySafe(request: RetryableRequest): boolean;
|
|
190
|
+
/**
|
|
191
|
+
* The policy the SDK ships with.
|
|
192
|
+
*
|
|
193
|
+
* Construction takes the same `RetryConfig` the SDK has always exported; what
|
|
194
|
+
* it no longer takes is a `retryCondition`, because a caller-supplied predicate
|
|
195
|
+
* over `(error, attempt)` structurally **cannot** see the request and so cannot
|
|
196
|
+
* express the rule this class exists for.
|
|
197
|
+
*/
|
|
198
|
+
declare class DefaultRetryPolicy implements RetryPolicy {
|
|
199
|
+
private readonly config;
|
|
200
|
+
constructor(config: RetryConfig);
|
|
201
|
+
shouldRetry(attempt: number, error: Error, request: RetryableRequest): boolean;
|
|
202
|
+
delayFor(attempt: number, error: Error): number;
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* The retry policy a client sends with, at `maxAttempts` attempts (the first included): the SDK's
|
|
206
|
+
* own, from `retryAttempts`, and the CLI's (REL-721).
|
|
207
|
+
*
|
|
208
|
+
* The backoff numbers are stated here once. The CLI ran a second policy beside this one —
|
|
209
|
+
* `axios-retry` with its own delays and its own reading of a refusal, which retried by numeric
|
|
210
|
+
* status — so a refusal one client gave up on, the other sent again. Now both ask this function, and
|
|
211
|
+
* a change to when a request is replayed reaches both.
|
|
212
|
+
*/
|
|
213
|
+
declare function retryPolicyFor(maxAttempts: number): RetryPolicy;
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* What a call site supplies beside the route it named.
|
|
217
|
+
*
|
|
218
|
+
* `path` is required exactly when the document's template has segments, and
|
|
219
|
+
* forbidden when it does not — so a call that forgot to scope a request, or one
|
|
220
|
+
* that scoped a request the route does not scope, is a compile error rather
|
|
221
|
+
* than a URL with a literal `{id}` in it.
|
|
222
|
+
*/
|
|
223
|
+
type SendOptions<P extends ApiPath, M extends ApiMethod<P>> = {
|
|
224
|
+
/**
|
|
225
|
+
* Query string values: exactly the names the document declares for this route, at the types it
|
|
226
|
+
* declares them (`QueryOf`, REL-687). An undeclared name is a compile error, not a 400.
|
|
227
|
+
*/
|
|
228
|
+
query?: QueryOf<P, M>;
|
|
229
|
+
body?: unknown;
|
|
230
|
+
/** Request headers this call adds, e.g. `If-Match` / `Idempotency-Key` (API4). */
|
|
231
|
+
headers?: Record<string, string>;
|
|
232
|
+
signal?: AbortSignal;
|
|
233
|
+
timeout?: number;
|
|
234
|
+
} & ([PathParamsOf<P, M>] extends [never] ? {
|
|
235
|
+
path?: never;
|
|
236
|
+
} : {
|
|
237
|
+
path: PathParamsOf<P, M>;
|
|
238
|
+
});
|
|
239
|
+
/**
|
|
240
|
+
* Core HTTP client for the RelyIO SDK.
|
|
241
|
+
*
|
|
242
|
+
* `send` is **the** typed entry: it names a route by the path template the
|
|
243
|
+
* committed document declares, and returns that operation's body type with the
|
|
244
|
+
* payload typed as `T`. `readPayload` from `@relyio/api-contract/client` is
|
|
245
|
+
* then the one unwrap — there is no second spelling and no runtime test of
|
|
246
|
+
* which envelope arrived (`INV-API-CLIENT-IS-DERIVED`).
|
|
247
|
+
*
|
|
248
|
+
* What the class makes public is what something outside it calls: `send` and `fetchPayload` are
|
|
249
|
+
* the requests; `configure`, `withHeaders` and `setApiKey` are the owning client's (`updateConfig()`,
|
|
250
|
+
* `with()`, `setApiKey()`). There are no verb helpers, no header or base-URL accessors and no
|
|
251
|
+
* untyped `request` (REL-717): a route is named by its document path, or it is not called.
|
|
252
|
+
*/
|
|
253
|
+
declare class HttpClient {
|
|
254
|
+
/** What every request reads off the configuration, derived by {@link configure} and nowhere else. */
|
|
255
|
+
protected connection: Connection;
|
|
256
|
+
/**
|
|
257
|
+
* Set on a view {@link withHeaders} made: the client every request is sent through, and the
|
|
258
|
+
* headers laid beneath each request's own. A view keeps no connection of its own that matters —
|
|
259
|
+
* everything it is asked to change, it changes on that client.
|
|
260
|
+
*/
|
|
261
|
+
protected view?: {
|
|
262
|
+
readonly client: ControlledHttpClient;
|
|
263
|
+
readonly headers: Readonly<Record<string, string>>;
|
|
264
|
+
/** The End user's token function this view asks in place of its client's, when it names one. */
|
|
265
|
+
readonly getToken?: EndUserTokenSource;
|
|
266
|
+
};
|
|
267
|
+
constructor(config: MergedRelyIOConfig);
|
|
268
|
+
protected announce(): void;
|
|
269
|
+
/**
|
|
270
|
+
* Send bytes to an address that is not a RelyIO route — an asset upload's presigned `PUT` and its
|
|
271
|
+
* multipart parts — through the client's configured `fetch`, so a wrapper that records, proxies or
|
|
272
|
+
* forbids network access sees every request this client makes (REL-2671). Nothing else is added: no
|
|
273
|
+
* key, no retry, no timeout, because the address belongs to the storage service, not to RelyIO.
|
|
274
|
+
*
|
|
275
|
+
* Static so the instances a customer holds stay `send` and `fetchPayload` (REL-3107). The `fetch`
|
|
276
|
+
* is read per call, from the client a view sends through, like the requests are.
|
|
277
|
+
*/
|
|
278
|
+
static transfer(client: HttpClient, url: string, init: RequestInit): Promise<Response>;
|
|
279
|
+
/**
|
|
280
|
+
* Call a route the committed document declares, and get back the body that
|
|
281
|
+
* document says it serves.
|
|
282
|
+
*/
|
|
283
|
+
send<P extends ApiPath, M extends ApiMethod<P>, T = unknown>(path: P, method: M, options: SendOptions<P, M>): Promise<ApiResponse<Wire<P, M, T>>>;
|
|
284
|
+
/**
|
|
285
|
+
* Call a route and return its payload — `send` plus the one unwrap, which is
|
|
286
|
+
* what all but a handful of call sites want.
|
|
287
|
+
*/
|
|
288
|
+
fetchPayload<P extends ApiPath, M extends ApiMethod<P>, T = unknown>(path: P, method: M, options: SendOptions<P, M>): Promise<Wire<P, M, T> extends {
|
|
289
|
+
data: unknown;
|
|
290
|
+
} ? Wire<P, M, T>['data'] : never>;
|
|
291
|
+
/**
|
|
292
|
+
* Make an HTTP request, retrying only where a retry is safe.
|
|
293
|
+
*
|
|
294
|
+
* The headers are merged **once**, before the first attempt, and every attempt
|
|
295
|
+
* sends that same object. That is what makes a minted `Idempotency-Key` stable
|
|
296
|
+
* across the retries of one logical write: the service sees one key and
|
|
297
|
+
* answers the replay from the original outcome instead of applying it twice.
|
|
298
|
+
*
|
|
299
|
+
* The loop always makes one attempt. The incumbent's `while (attempt <
|
|
300
|
+
* maxAttempts)` made none at `retryAttempts: 0` — a value the config validator
|
|
301
|
+
* then accepted, and now refuses — and then dereferenced an unassigned
|
|
302
|
+
* `lastError`. `retryAttempts` is the most attempts, the first included; an
|
|
303
|
+
* `HttpClient` constructed directly is not validated, so the first attempt
|
|
304
|
+
* stays unconditional.
|
|
305
|
+
*
|
|
306
|
+
* A request that would carry no `X-API-Key` makes none: it rejects with
|
|
307
|
+
* `MissingApiKeyError` before `fetch` is called, is not retried, and names
|
|
308
|
+
* `apiKey`. This is where a client built without a key learns it — the
|
|
309
|
+
* constructor accepts one so that a module-scope client can be evaluated by a
|
|
310
|
+
* build that holds no key (REL-1120) — and so does one whose `setApiKey` was
|
|
311
|
+
* given none.
|
|
312
|
+
*
|
|
313
|
+
* The End user's token is asked for once, after the key is known to be there and before the first
|
|
314
|
+
* attempt — of `getToken`, the nearest view's function, or the configuration's — so every retry
|
|
315
|
+
* of one call sends the token its first attempt sent. See `RelyIOConfig.getToken`.
|
|
316
|
+
*/
|
|
317
|
+
private request;
|
|
318
|
+
/**
|
|
319
|
+
* One attempt.
|
|
320
|
+
*
|
|
321
|
+
* The caller's `signal` and the timeout both end it, through one controller.
|
|
322
|
+
* The incumbent handed `fetch` the caller's signal **instead of** its own, so a
|
|
323
|
+
* request that passed a signal had no timeout at all — and a caller abort then
|
|
324
|
+
* surfaced as `Request timed out`. `timedOut` is what tells the two apart: a
|
|
325
|
+
* timeout is a `TimeoutError`, a caller abort is the platform's `AbortError`,
|
|
326
|
+
* rethrown as it came.
|
|
327
|
+
*/
|
|
328
|
+
private makeRequest;
|
|
329
|
+
/**
|
|
330
|
+
* The headers without those a browser controls itself, when this is a browser; unchanged when it
|
|
331
|
+
* is not.
|
|
332
|
+
*
|
|
333
|
+
* A name is matched without regard to case (RFC 9110 §5.1), because a browser refuses `Origin`
|
|
334
|
+
* whatever case a page spells it in. This matched only the all-lowercase and all-uppercase
|
|
335
|
+
* spellings, so `Origin` and `User-Agent` — how people write them — reached `fetch` (REL-1409).
|
|
336
|
+
*/
|
|
337
|
+
private filterHeadersForBrowser;
|
|
338
|
+
/**
|
|
339
|
+
* Prepare request body based on content type and environment
|
|
340
|
+
*/
|
|
341
|
+
private prepareRequestBody;
|
|
342
|
+
private parseResponse;
|
|
343
|
+
private resolveUrl;
|
|
344
|
+
private mergeRequestConfig;
|
|
345
|
+
private buildQueryString;
|
|
346
|
+
private headersToObject;
|
|
347
|
+
private log;
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* The client the SDK's own factory holds: {@link HttpClient} plus the three things only the owning
|
|
351
|
+
* client does to it — `configure` (`updateConfig()`), `withHeaders` (`with()`) and `setApiKey`
|
|
352
|
+
* (`setApiKey()`). It is not exported from the package, so a customer holding the client from
|
|
353
|
+
* `getHttpClient()` sees `send` and `fetchPayload` and nothing else (REL-3107).
|
|
354
|
+
*/
|
|
355
|
+
declare class ControlledHttpClient extends HttpClient {
|
|
356
|
+
/**
|
|
357
|
+
* A view of this client that sends every request through it with `headers` added — beneath the
|
|
358
|
+
* request's own, over this client's — and changes nothing else: its key, base URL, timeout,
|
|
359
|
+
* retries and `fetch` are this client's as they are when each request is sent, so a later
|
|
360
|
+
* `configure()` or `setApiKey()` on this client reaches the view, and one on the view reaches
|
|
361
|
+
* this client. `client.with({ dataset })` is one (REL-2417).
|
|
362
|
+
*
|
|
363
|
+
* `options.getToken` is the End user's token function the view asks in place of this client's,
|
|
364
|
+
* and of any view it is made from: `client.with({ getToken })` (REL-3882).
|
|
365
|
+
*/
|
|
366
|
+
withHeaders(headers: Readonly<Record<string, string>>, options?: {
|
|
367
|
+
readonly getToken?: EndUserTokenSource;
|
|
368
|
+
}): ControlledHttpClient;
|
|
369
|
+
/**
|
|
370
|
+
* Run on `config` from now on: every later request — whoever holds this client — is sent to its
|
|
371
|
+
* `baseUrl`, with its headers, key and dataset, its timeout and retries, through its `fetch`, and
|
|
372
|
+
* logged by its `debug` options.
|
|
373
|
+
*
|
|
374
|
+
* The client's own `updateConfig()` calls this rather than building a second client, so a
|
|
375
|
+
* `content()` or other sub-client taken before the update sends through the update too. Anything
|
|
376
|
+
* set on this client since it was last configured — `setApiKey()` — is replaced by what `config`
|
|
377
|
+
* says.
|
|
378
|
+
*/
|
|
379
|
+
configure(config: MergedRelyIOConfig): void;
|
|
380
|
+
/**
|
|
381
|
+
* Replace the key every later request sends as `X-API-Key`, and the `apiKey` the configuration
|
|
382
|
+
* reports.
|
|
383
|
+
*
|
|
384
|
+
* The key is read the way construction reads it (`presentCredential`): an absent, empty or blank key is
|
|
385
|
+
* no key, so the header is dropped and the next request is refused with `MissingApiKeyError`
|
|
386
|
+
* before anything is sent. A key header of any other spelling — one given in `headers` — goes
|
|
387
|
+
* too, from the headers sent and from the headers the configuration reports, so the old key is
|
|
388
|
+
* never sent beside the new one.
|
|
389
|
+
*
|
|
390
|
+
* The configuration is the one the owning `TypedRelyIOClient` holds (it hands its own to the
|
|
391
|
+
* constructor), so its `getConfig().apiKey` — what a surface reads to decide a key's kind — is the
|
|
392
|
+
* key sent from here on.
|
|
393
|
+
*
|
|
394
|
+
* This replaced `setAuthToken`, which added `Authorization: Bearer` beside an `X-API-Key` it
|
|
395
|
+
* never touched: the API reads `X-API-Key` first, so the old key was the one used (REL-1385).
|
|
396
|
+
*/
|
|
397
|
+
setApiKey(apiKey: string): void;
|
|
398
|
+
}
|
|
399
|
+
/** What `getToken` is: asked per call, answering the signed-in End user's token or `null`. */
|
|
400
|
+
type EndUserTokenSource = NonNullable<MergedRelyIOConfig['getToken']>;
|
|
401
|
+
/** Everything a request reads off the configuration, replaced whole whenever it changes. */
|
|
402
|
+
interface Connection {
|
|
403
|
+
/**
|
|
404
|
+
* The configuration itself, for what is read per request — `fetch`, the `browser` options,
|
|
405
|
+
* `debug` — and the object `setApiKey` writes the key into, which the owning client reports.
|
|
406
|
+
*/
|
|
407
|
+
readonly config: MergedRelyIOConfig;
|
|
408
|
+
readonly baseUrl: string;
|
|
409
|
+
readonly defaultHeaders: Readonly<Record<string, string>>;
|
|
410
|
+
readonly timeout: number;
|
|
411
|
+
readonly retryPolicy: RetryPolicy;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* Any schema the client can be typed by. `any` is deliberate: the client accepts whatever content
|
|
416
|
+
* types, singletons, queries and settings groups a customer declares.
|
|
417
|
+
*
|
|
418
|
+
* This is the constraint `TypedRelyIOClient`'s `TSchema` carries, so `RegisteredSchema` is inferred
|
|
419
|
+
* against the same one — `createClient` returns `TypedRelyIOClient<RegisteredSchema>`, and a schema
|
|
420
|
+
* inferred against any wider alias would not be provably inside the client's bound.
|
|
421
|
+
*/
|
|
422
|
+
type AnySchemaDefinition = SchemaDefinition<any, any, any, any, any>;
|
|
423
|
+
/**
|
|
424
|
+
* The seam `relyio generate-types` fills (`INV-SS-TYPES-INFERRED`).
|
|
425
|
+
*
|
|
426
|
+
* Empty here on purpose. The generated `schema.ts` augments it —
|
|
427
|
+
*
|
|
428
|
+
* ```ts
|
|
429
|
+
* declare module '@relyio/sdk' {
|
|
430
|
+
* interface Register {
|
|
431
|
+
* schema: typeof schema
|
|
432
|
+
* }
|
|
433
|
+
* }
|
|
434
|
+
* ```
|
|
435
|
+
*
|
|
436
|
+
* — and from then on `createClient({ apiKey })`, with no generic and no `schema:`, is typed by that
|
|
437
|
+
* schema. The pattern is TanStack Router's. It is type-level only: nothing at runtime reads it.
|
|
438
|
+
*/
|
|
439
|
+
interface Register {
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
442
|
+
* The schema the project registered through `Register`, or `undefined` when nothing did — which is
|
|
443
|
+
* the untyped client, exactly as before the generator ran.
|
|
444
|
+
*/
|
|
445
|
+
type RegisteredSchema = Register extends {
|
|
446
|
+
schema: infer S extends AnySchemaDefinition;
|
|
447
|
+
} ? S : undefined;
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* One Record type's Records over the Content API (spec 1, REL-3771): `relyio.records('order')`.
|
|
451
|
+
*
|
|
452
|
+
* A Record is an app's data — an order, a stock level, a cart — written by your server with a
|
|
453
|
+
* secret key and live the moment it lands: no draft, no publication, no locale, no version. A
|
|
454
|
+
* publishable key lists and gets the Records a Record type's read Policy grants it — anonymously, or
|
|
455
|
+
* as the End user whose token it carries (spec 4, REL-3883) — and creates, updates and deletes them
|
|
456
|
+
* only as that End user, as the type's write Policy allows (REL-3884): RelyIO sets the owner from
|
|
457
|
+
* the token. With no token it writes nothing (`PUBLISHABLE_KEY_REFUSED`).
|
|
458
|
+
*
|
|
459
|
+
* Every body is read from the committed document (`INV-API-CLIENT-IS-DERIVED`): the paths are the
|
|
460
|
+
* document's, and `RecordMetadata` is its schema by name.
|
|
461
|
+
*/
|
|
462
|
+
|
|
463
|
+
/** What the API reports about a Record beside its fields. */
|
|
464
|
+
type RecordMetadata = components['schemas']['RecordMetadata'];
|
|
465
|
+
/** A Record as a read answers it: its fields at the top level, and `_metadata`. */
|
|
466
|
+
type BaseRecord = Record<string, unknown> & {
|
|
467
|
+
_metadata: RecordMetadata;
|
|
468
|
+
};
|
|
469
|
+
/** What a Record read and write take beside their own options. */
|
|
470
|
+
interface RecordCallOptions {
|
|
471
|
+
/** The dataset, by id or name, over the client's. */
|
|
472
|
+
dataset?: string;
|
|
473
|
+
signal?: AbortSignal;
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* What a Record read may expand (REL-3772): include paths, in the grammar `content().get` takes —
|
|
477
|
+
* `lines.product`, `convertedTo.lines.product(title)`. A reference to an entry expands into the
|
|
478
|
+
* entry as this key reads it — published with a secret key, its draft with a preview key — and one
|
|
479
|
+
* to a Record into the Record. A reference to an entry the key cannot see, or that is gone, reads
|
|
480
|
+
* as `null` — unless it is a pinned reference (`pin: 'version'`), which reads the entry as it was
|
|
481
|
+
* at the version the Record pinned, whatever it has done since, its `_metadata.pin` naming that
|
|
482
|
+
* version and the one the key reads now (REL-3776).
|
|
483
|
+
*/
|
|
484
|
+
interface RecordReadOptions extends RecordCallOptions {
|
|
485
|
+
include?: readonly string[];
|
|
486
|
+
/**
|
|
487
|
+
* The locale each included entry is composed in, with its schema's fallback; the schema's
|
|
488
|
+
* default when absent. A Record has no locale of its own (REL-3776).
|
|
489
|
+
*/
|
|
490
|
+
locale?: string;
|
|
491
|
+
}
|
|
492
|
+
/** What an update takes: the revision the change was made on. */
|
|
493
|
+
interface RecordUpdateOptions extends RecordCallOptions {
|
|
494
|
+
/**
|
|
495
|
+
* `_metadata.revision` of the read the change was made on. A Record written since is refused
|
|
496
|
+
* with `ResourceConflictError`, its `blockage` (`revision-stale`) naming the fields that changed,
|
|
497
|
+
* and nothing is written.
|
|
498
|
+
*/
|
|
499
|
+
revision: number;
|
|
500
|
+
}
|
|
501
|
+
/** A list's options: one page of the Records `where` keeps, in one order. */
|
|
502
|
+
interface RecordListOptions<TFields> extends RecordReadOptions {
|
|
503
|
+
/** Field conditions, in the grammar `content().list({ where })` takes. */
|
|
504
|
+
where?: {
|
|
505
|
+
[K in keyof TFields]?: unknown;
|
|
506
|
+
} & Record<string, unknown>;
|
|
507
|
+
/** One key: `createdAt`, `updatedAt`, `id` or a field holding one value. Default `createdAt:desc`. */
|
|
508
|
+
sort?: `${'createdAt' | 'updatedAt' | 'id' | (keyof TFields & string)}:${'asc' | 'desc'}`;
|
|
509
|
+
limit?: number;
|
|
510
|
+
cursor?: string;
|
|
511
|
+
/** Answer `meta.count`: how many Records `where` keeps across every page. */
|
|
512
|
+
count?: boolean;
|
|
513
|
+
/** Count the values of each of these fields among the Records `where` keeps, under `meta.facets`. */
|
|
514
|
+
facets?: ReadonlyArray<keyof TFields & string>;
|
|
515
|
+
}
|
|
516
|
+
/** What an upload of a Record's File takes beside its bytes (REL-3824). */
|
|
517
|
+
interface RecordFileUploadOptions extends RecordCallOptions {
|
|
518
|
+
/** The File's name, as a read will answer it. */
|
|
519
|
+
name: string;
|
|
520
|
+
/** Its media type: one the field's `accept` admits. */
|
|
521
|
+
type: string;
|
|
522
|
+
}
|
|
523
|
+
/** The bytes of a File: what `fetch` sends as a body, of a length known before it is sent. */
|
|
524
|
+
type RecordFileBytes = Blob | ArrayBuffer | Uint8Array;
|
|
525
|
+
/** One facet's buckets: each value and how many Records hold it, most frequent first. */
|
|
526
|
+
interface RecordFacetBucket {
|
|
527
|
+
readonly value: unknown;
|
|
528
|
+
readonly count: number;
|
|
529
|
+
}
|
|
530
|
+
/** One page of a Record list, as the API answers it. */
|
|
531
|
+
interface RecordList<T> {
|
|
532
|
+
data: T[];
|
|
533
|
+
page: {
|
|
534
|
+
nextCursor: string | null;
|
|
535
|
+
hasMore: boolean;
|
|
536
|
+
};
|
|
537
|
+
meta: {
|
|
538
|
+
count?: number;
|
|
539
|
+
facets?: Record<string, RecordFacetBucket[]>;
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
/**
|
|
543
|
+
* One Record type's Records.
|
|
544
|
+
*
|
|
545
|
+
* @example
|
|
546
|
+
* ```typescript
|
|
547
|
+
* const orders = relyio.records('order')
|
|
548
|
+
* const order = await orders.create({ shopperEmail: 'ada@example.com', status: 'paid', total: 19 })
|
|
549
|
+
* const { data: paid, meta } = await orders.list({
|
|
550
|
+
* where: { status: 'paid' },
|
|
551
|
+
* sort: 'createdAt:asc',
|
|
552
|
+
* count: true,
|
|
553
|
+
* })
|
|
554
|
+
* ```
|
|
555
|
+
*/
|
|
556
|
+
declare class RecordsClient<TRecord extends BaseRecord = BaseRecord, TInput = Record<string, unknown>,
|
|
557
|
+
/** What an update takes: the create's values without the type's Counters (REL-3765). */
|
|
558
|
+
TUpdate = TInput> {
|
|
559
|
+
private readonly httpClient;
|
|
560
|
+
readonly recordType: string;
|
|
561
|
+
constructor(httpClient: HttpClient, recordType: string);
|
|
562
|
+
/**
|
|
563
|
+
* Store a Record, live the moment it lands. Values that break the Record type's fields are
|
|
564
|
+
* refused with `ValidationError` naming each field, and nothing is stored. Values a unique set of
|
|
565
|
+
* the type claims, held by another Record, are refused with `ResourceConflictError` — its
|
|
566
|
+
* `blockage` is `{ kind: 'value-taken', facts: { fields, recordId } }` (REL-3782) — with no
|
|
567
|
+
* `recordId` for an End user's write, whose read Policy may hide the holder (REL-3863).
|
|
568
|
+
*/
|
|
569
|
+
create(fields: TInput, options?: RecordCallOptions): Promise<TRecord>;
|
|
570
|
+
/** One page of the Records `where` keeps, newest first unless `sort` names one key. */
|
|
571
|
+
list(options?: RecordListOptions<Omit<TRecord, '_metadata'>>): Promise<RecordList<TRecord>>;
|
|
572
|
+
/**
|
|
573
|
+
* Upload a File for one of this Record type's file fields (REL-3824), and answer what a create or
|
|
574
|
+
* update sets the field to. The bytes go straight to object storage at the address the upload
|
|
575
|
+
* names, never through RelyIO; a File outside the field's `accept` or over its `maxSize` is
|
|
576
|
+
* refused with `ValidationError` naming the field before any byte is sent. An upload no write
|
|
577
|
+
* names within a day is deleted with its bytes. Server-side only, as every Record call is.
|
|
578
|
+
*
|
|
579
|
+
* @example
|
|
580
|
+
* ```typescript
|
|
581
|
+
* const invoice = await relyio.records('order').upload('invoice', pdfBytes, {
|
|
582
|
+
* name: 'invoice-1001.pdf',
|
|
583
|
+
* type: 'application/pdf',
|
|
584
|
+
* })
|
|
585
|
+
* await relyio.records('order').update(id, { invoice }, { revision })
|
|
586
|
+
* ```
|
|
587
|
+
*/
|
|
588
|
+
upload(field: string, bytes: RecordFileBytes, options: RecordFileUploadOptions): Promise<FileUpload>;
|
|
589
|
+
/**
|
|
590
|
+
* One Record by its id, or `null` when none is stored. A Record type nobody deployed throws
|
|
591
|
+
* `ResourceNotFoundError` naming it, so it never reads as a missing Record.
|
|
592
|
+
*/
|
|
593
|
+
get(id: string, options?: RecordReadOptions): Promise<TRecord | null>;
|
|
594
|
+
/**
|
|
595
|
+
* Change the fields named, against the revision the change was made on, and answer the Record at
|
|
596
|
+
* its next revision. A field given `null` is removed; one not named keeps its value. A Record
|
|
597
|
+
* written since `revision` is refused with `ResourceConflictError` — its `blockage` is
|
|
598
|
+
* `{ kind: 'revision-stale', facts: { changedFields } }` — and nothing is written. Values that
|
|
599
|
+
* break the Record type are refused with `ValidationError` naming each field, and values another
|
|
600
|
+
* Record holds under a unique set with `ResourceConflictError`, its blockage `value-taken`.
|
|
601
|
+
*
|
|
602
|
+
* @example
|
|
603
|
+
* ```typescript
|
|
604
|
+
* const order = await relyio.records('order').get(id)
|
|
605
|
+
* await relyio.records('order').update(id, { status: 'shipped' }, {
|
|
606
|
+
* revision: order._metadata.revision,
|
|
607
|
+
* })
|
|
608
|
+
* ```
|
|
609
|
+
*/
|
|
610
|
+
update(id: string, fields: {
|
|
611
|
+
[K in keyof TUpdate]?: TUpdate[K] | null;
|
|
612
|
+
}, options: RecordUpdateOptions): Promise<TRecord>;
|
|
613
|
+
/**
|
|
614
|
+
* Remove a Record. Another Record holding it through a reference declared to block its delete —
|
|
615
|
+
* the default — is refused with `ResourceConflictError`, its `blockage`
|
|
616
|
+
* (`{ kind: 'referenced', facts: { referrers } }`) naming each; one declared `nullify` is cleared.
|
|
617
|
+
*/
|
|
618
|
+
delete(id: string, options?: RecordCallOptions): Promise<void>;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
/**
|
|
622
|
+
* `@relyio/sdk/automations` — an Automation, declared (spec 3, REL-3845; ADR 0021).
|
|
623
|
+
*
|
|
624
|
+
* One file per Automation at `relyio/automations/<name>.ts`, default-exporting what
|
|
625
|
+
* {@link defineAutomation} returns; the file's name is the Automation's. An Automation is a trigger
|
|
626
|
+
* and a sequence of steps, and all of it is data: `steps` is called **once**, when the Manifest
|
|
627
|
+
* loads, with typed references to the event and to earlier steps' outputs, and returns plain data.
|
|
628
|
+
* No code of yours runs when an event arrives, except inside a Function step.
|
|
629
|
+
*
|
|
630
|
+
* @example
|
|
631
|
+
* // relyio/automations/order-paid.ts
|
|
632
|
+
* import { defineAutomation, fields, or, records, tpl } from '@relyio/sdk/automations'
|
|
633
|
+
* import reserveStock from '../functions/reserve-stock'
|
|
634
|
+
*
|
|
635
|
+
* export default defineAutomation({
|
|
636
|
+
* on: records.order.updated({ where: { status: { becomes: 'paid' } } }),
|
|
637
|
+
* steps: ({ event, step }) => {
|
|
638
|
+
* const reserve = step.fn('reserve', reserveStock, { orderId: event.record._metadata.id })
|
|
639
|
+
* const fraud = step.agent('fraud', {
|
|
640
|
+
* instructions: tpl`Read order ${event.record.number} and the customer's past orders`,
|
|
641
|
+
* context: { order: event.record },
|
|
642
|
+
* output: { verdict: fields.enum({ options: { 'looks fine': 'Looks fine', 'needs review': 'Needs review' } }) },
|
|
643
|
+
* })
|
|
644
|
+
* return [reserve, fraud, step.if('ship', fraud.output.verdict.eq('looks fine'), { then: […] })]
|
|
645
|
+
* },
|
|
646
|
+
* })
|
|
647
|
+
*
|
|
648
|
+
* **Typed.** Once `relyio generate-types` has registered the schema (`Register`), `records.<type>`
|
|
649
|
+
* takes only the Record types it declares and `event.record` is typed by its fields. A Function
|
|
650
|
+
* step takes the Function by import, so its input is checked against the Function's input Fields and
|
|
651
|
+
* its `output` has exactly the Function's output Fields: a misspelt one is a type error, and so is a
|
|
652
|
+
* step reading one declared after it. The plan refuses both again, for a Manifest read as data.
|
|
653
|
+
*
|
|
654
|
+
* **Conditions** are the list grammar's operators (`eq`, `ne`, `in`, `gt`, `exists`, …) on a
|
|
655
|
+
* reference, combined with {@link and}, {@link or} and {@link not}. `becomes` — changed in this
|
|
656
|
+
* write and now equal — is a trigger's `where` alone.
|
|
657
|
+
*
|
|
658
|
+
* **Extensions** (REL-3846): `relyio/extensions.ts` declares the Extensions the Project uses with
|
|
659
|
+
* `defineExtensions` (`@relyio/sdk/extensions`), and hands out a typed handle for each. An Automation
|
|
660
|
+
* imports it: `on: stripe.paymentIntentSucceeded()` starts on one of its Triggers, and
|
|
661
|
+
* `step.action('email', resend.send, { … })` sends through one of its Actions — input and output
|
|
662
|
+
* typed by the Extension. Using an Extension the file does not declare is refused by the plan.
|
|
663
|
+
*/
|
|
664
|
+
|
|
665
|
+
/** What a Record event says happened to one Record. */
|
|
666
|
+
type AutomationRecordAction = 'created' | 'updated' | 'deleted' | 'expired';
|
|
667
|
+
/** What an entry event says happened to one entry. */
|
|
668
|
+
type AutomationEntryAction = 'created' | 'updated' | 'published' | 'unpublished' | 'deleted' | 'archived' | 'restored';
|
|
669
|
+
/** A `becomes` test: changed in this write and now equal. A trigger's `where` alone holds one. */
|
|
670
|
+
interface AutomationBecomesTest {
|
|
671
|
+
readonly root: 'event';
|
|
672
|
+
readonly path: readonly string[];
|
|
673
|
+
readonly operator: 'becomes';
|
|
674
|
+
readonly value: ConditionScalar;
|
|
675
|
+
}
|
|
676
|
+
/** A trigger's `where`: a condition over `event`, whose tests may also be `becomes`. */
|
|
677
|
+
type AutomationWhere = ConditionTest | AutomationBecomesTest | {
|
|
678
|
+
readonly and: readonly AutomationWhere[];
|
|
679
|
+
} | {
|
|
680
|
+
readonly or: readonly AutomationWhere[];
|
|
681
|
+
} | {
|
|
682
|
+
readonly not: AutomationWhere;
|
|
683
|
+
};
|
|
684
|
+
/** An Automation's trigger, as data. */
|
|
685
|
+
type AutomationTrigger = {
|
|
686
|
+
readonly kind: 'record';
|
|
687
|
+
readonly recordType: string;
|
|
688
|
+
readonly action: AutomationRecordAction;
|
|
689
|
+
readonly where?: AutomationWhere;
|
|
690
|
+
} | {
|
|
691
|
+
readonly kind: 'entry';
|
|
692
|
+
readonly contentType: string;
|
|
693
|
+
readonly action: AutomationEntryAction;
|
|
694
|
+
readonly where?: AutomationWhere;
|
|
695
|
+
}
|
|
696
|
+
/**
|
|
697
|
+
* An entry of any content type: `where` is a plain condition over {@link AnyEntryEvent} — the
|
|
698
|
+
* entry's uid and content type, and what changed — with no `becomes`.
|
|
699
|
+
*/
|
|
700
|
+
| {
|
|
701
|
+
readonly kind: 'anyEntry';
|
|
702
|
+
readonly action: AutomationEntryAction;
|
|
703
|
+
readonly where?: ConditionTree;
|
|
704
|
+
} | {
|
|
705
|
+
readonly kind: 'schedule';
|
|
706
|
+
readonly cron: string;
|
|
707
|
+
readonly timezone: string;
|
|
708
|
+
}
|
|
709
|
+
/** Another Automation's run failed; `where` is a plain condition over {@link RunFailedEvent}. */
|
|
710
|
+
| {
|
|
711
|
+
readonly kind: 'runFailed';
|
|
712
|
+
readonly where?: ConditionTree;
|
|
713
|
+
}
|
|
714
|
+
/**
|
|
715
|
+
* One of an Extension's Triggers: `trigger` is its slug in the Extension catalog. `where` is a plain
|
|
716
|
+
* condition over {@link ExtensionTriggerEvent}.
|
|
717
|
+
*/
|
|
718
|
+
| {
|
|
719
|
+
readonly kind: 'extension';
|
|
720
|
+
readonly extension: string;
|
|
721
|
+
readonly trigger: string;
|
|
722
|
+
readonly where?: ConditionTree;
|
|
723
|
+
};
|
|
724
|
+
/** A JSON value, as a step's input holds it literally. */
|
|
725
|
+
type AutomationJson = string | number | boolean | null | readonly AutomationJson[] | {
|
|
726
|
+
readonly [key: string]: AutomationJson;
|
|
727
|
+
};
|
|
728
|
+
/** A reference: the value at `path` under `root` — `event`, or an earlier step's name. */
|
|
729
|
+
interface AutomationRef {
|
|
730
|
+
readonly kind: 'ref';
|
|
731
|
+
readonly root: string;
|
|
732
|
+
readonly path: readonly string[];
|
|
733
|
+
}
|
|
734
|
+
/** A value a step is handed, as data. */
|
|
735
|
+
type AutomationValue = {
|
|
736
|
+
readonly kind: 'literal';
|
|
737
|
+
readonly value: AutomationJson;
|
|
738
|
+
} | AutomationRef | {
|
|
739
|
+
readonly kind: 'template';
|
|
740
|
+
readonly parts: ReadonlyArray<string | AutomationRef>;
|
|
741
|
+
} | {
|
|
742
|
+
readonly kind: 'list';
|
|
743
|
+
readonly items: readonly AutomationValue[];
|
|
744
|
+
} | {
|
|
745
|
+
readonly kind: 'object';
|
|
746
|
+
readonly fields: Readonly<Record<string, AutomationValue>>;
|
|
747
|
+
};
|
|
748
|
+
/** A step's input, by key. */
|
|
749
|
+
type AutomationInput = Readonly<Record<string, AutomationValue>>;
|
|
750
|
+
/** A Wait's length: whole minutes, hours or days, at most 30 days. */
|
|
751
|
+
type AutomationDuration = `${number}m` | `${number}h` | `${number}d`;
|
|
752
|
+
/** One Record or entry by its uid: what a Look-up reads. */
|
|
753
|
+
type AutomationItemRef = {
|
|
754
|
+
readonly kind: 'record';
|
|
755
|
+
readonly recordType: string;
|
|
756
|
+
readonly uid: AutomationValue;
|
|
757
|
+
} | {
|
|
758
|
+
readonly kind: 'entry';
|
|
759
|
+
readonly contentType: string;
|
|
760
|
+
readonly uid: AutomationValue;
|
|
761
|
+
};
|
|
762
|
+
/** Which of an entry's states a Look-up reads: what readers see, or the editor's view. */
|
|
763
|
+
type AutomationLookupState = 'published' | 'draft';
|
|
764
|
+
/** A type an Agent step may propose a change to: a content type's entries, or a Record type's. */
|
|
765
|
+
type AutomationProposeTarget = {
|
|
766
|
+
readonly kind: 'entry';
|
|
767
|
+
readonly contentType: string;
|
|
768
|
+
} | {
|
|
769
|
+
readonly kind: 'record';
|
|
770
|
+
readonly recordType: string;
|
|
771
|
+
};
|
|
772
|
+
/** One Automation step, as data. */
|
|
773
|
+
type AutomationStep = {
|
|
774
|
+
readonly kind: 'function';
|
|
775
|
+
readonly name: string;
|
|
776
|
+
readonly function: string;
|
|
777
|
+
readonly input: AutomationInput;
|
|
778
|
+
} | {
|
|
779
|
+
readonly kind: 'agent';
|
|
780
|
+
readonly name: string;
|
|
781
|
+
readonly agent?: string;
|
|
782
|
+
readonly instructions: AutomationValue;
|
|
783
|
+
readonly context: AutomationInput;
|
|
784
|
+
readonly output: readonly object[];
|
|
785
|
+
readonly propose?: readonly AutomationProposeTarget[];
|
|
786
|
+
} | {
|
|
787
|
+
readonly kind: 'action';
|
|
788
|
+
readonly name: string;
|
|
789
|
+
readonly extension: string;
|
|
790
|
+
readonly action: string;
|
|
791
|
+
readonly input: AutomationInput;
|
|
792
|
+
} | {
|
|
793
|
+
readonly kind: 'lookup';
|
|
794
|
+
readonly name: string;
|
|
795
|
+
readonly item: Extract<AutomationItemRef, {
|
|
796
|
+
readonly kind: 'record';
|
|
797
|
+
}>;
|
|
798
|
+
} | {
|
|
799
|
+
readonly kind: 'lookup';
|
|
800
|
+
readonly name: string;
|
|
801
|
+
readonly item: Extract<AutomationItemRef, {
|
|
802
|
+
readonly kind: 'entry';
|
|
803
|
+
}>;
|
|
804
|
+
readonly state: AutomationLookupState;
|
|
805
|
+
} | {
|
|
806
|
+
readonly kind: 'if';
|
|
807
|
+
readonly name: string;
|
|
808
|
+
readonly condition: ConditionTree;
|
|
809
|
+
readonly then: readonly AutomationStep[];
|
|
810
|
+
readonly else: readonly AutomationStep[];
|
|
811
|
+
} | {
|
|
812
|
+
readonly kind: 'wait';
|
|
813
|
+
readonly name: string;
|
|
814
|
+
readonly duration: AutomationDuration;
|
|
815
|
+
} | {
|
|
816
|
+
readonly kind: 'ask';
|
|
817
|
+
readonly name: string;
|
|
818
|
+
readonly to: readonly string[];
|
|
819
|
+
readonly question: AutomationValue;
|
|
820
|
+
readonly choices: readonly string[];
|
|
821
|
+
readonly timeout: AutomationDuration;
|
|
822
|
+
readonly onTimeout?: string;
|
|
823
|
+
readonly sample?: string;
|
|
824
|
+
readonly approve?: AutomationRef;
|
|
825
|
+
};
|
|
826
|
+
/** What an Automation declares, as data: what the Manifest carries for one file. */
|
|
827
|
+
interface AutomationDefinition {
|
|
828
|
+
readonly description?: string;
|
|
829
|
+
readonly on: AutomationTrigger;
|
|
830
|
+
/** What each run is about: one Record or entry (REL-3853). */
|
|
831
|
+
readonly about?: AutomationItemRef;
|
|
832
|
+
readonly steps: readonly AutomationStep[];
|
|
833
|
+
}
|
|
834
|
+
/** Where a reference's value type rides, for the compiler alone. */
|
|
835
|
+
declare const REF_VALUE: unique symbol;
|
|
836
|
+
/** A list is compared by its members, as a condition compares a list. */
|
|
837
|
+
type MemberOf<T> = T extends ReadonlyArray<infer Member> ? Member : T;
|
|
838
|
+
/** The values a path holding `T` is compared with. */
|
|
839
|
+
type ComparedWith<T> = unknown extends T ? ConditionScalar : MemberOf<NonNullable<T>> extends infer Member ? Member extends ConditionScalar ? Member : never : never;
|
|
840
|
+
/** `becomes`, offered on a trigger's `where` alone. */
|
|
841
|
+
interface BecomesTest<T> {
|
|
842
|
+
/** Changed in this write, and now equal to `value`. */
|
|
843
|
+
becomes(value: ComparedWith<T>): AutomationBecomesTest;
|
|
844
|
+
}
|
|
845
|
+
/**
|
|
846
|
+
* The paths under a reference to `T`. Where its shape is not known — no schema registered — a
|
|
847
|
+
* reference is `any`: an index signature would read every step as possibly undefined under
|
|
848
|
+
* `noUncheckedIndexedAccess`, and a typed project registers its schema.
|
|
849
|
+
*/
|
|
850
|
+
type RefChildren<T, B extends boolean> = unknown extends T ? any : NonNullable<T> extends readonly unknown[] ? unknown : NonNullable<T> extends object ? {
|
|
851
|
+
readonly [K in keyof NonNullable<T> & string]-?: Ref<NonNullable<T>[K], B>;
|
|
852
|
+
} : unknown;
|
|
853
|
+
/**
|
|
854
|
+
* A reference to a value of type `T` when the step runs: hand it to a step's input, interpolate it
|
|
855
|
+
* with {@link tpl}, or test it (`.eq(…)`, `.in([…])`, `.exists()`) for a condition. A field named like
|
|
856
|
+
* a test (`eq`, `in`) is not reachable as a path. `B`: whether `becomes` is offered (a `where`).
|
|
857
|
+
*/
|
|
858
|
+
type Ref<T, B extends boolean = false> = {
|
|
859
|
+
readonly [REF_VALUE]: T;
|
|
860
|
+
} & ConditionTests<T> & (B extends true ? BecomesTest<T> : unknown) & RefChildren<T, B>;
|
|
861
|
+
/** A reference whose value is a `T`: what a step's input takes where it takes a `T`. */
|
|
862
|
+
type RefTo<T> = {
|
|
863
|
+
readonly [REF_VALUE]: T;
|
|
864
|
+
};
|
|
865
|
+
/** Where a template's parts ride at run time. */
|
|
866
|
+
declare const TEMPLATE: unique symbol;
|
|
867
|
+
/** Text written with {@link tpl}: literal parts and references, rendered when the step runs. */
|
|
868
|
+
interface AutomationTemplate {
|
|
869
|
+
readonly [TEMPLATE]: ReadonlyArray<string | AutomationRef>;
|
|
870
|
+
}
|
|
871
|
+
/** What a step's input takes where its Field takes a `T`. */
|
|
872
|
+
type Accepts<T> = T | RefTo<T> | (string extends NonNullable<T> ? AutomationTemplate : never) | AcceptsWithin<NonNullable<T>>;
|
|
873
|
+
/**
|
|
874
|
+
* A list or object a step's input takes, references at any depth. Distributed over each member of
|
|
875
|
+
* `T`, so a union — `AutomationJson`, a webhook's body — takes a list or an object holding
|
|
876
|
+
* references, as one member alone does.
|
|
877
|
+
*/
|
|
878
|
+
type AcceptsWithin<T> = T extends ReadonlyArray<infer Member> ? ReadonlyArray<Accepts<Member>> : T extends object ? {
|
|
879
|
+
readonly [K in keyof T]: Accepts<T[K]>;
|
|
880
|
+
} : never;
|
|
881
|
+
/** A step's input: each key as its Field types it, a value or a reference to one. */
|
|
882
|
+
type StepInput<V> = {
|
|
883
|
+
readonly [K in keyof V]: Accepts<V[K]>;
|
|
884
|
+
};
|
|
885
|
+
/**
|
|
886
|
+
* Text with references in it, rendered when the step runs.
|
|
887
|
+
*
|
|
888
|
+
* @example
|
|
889
|
+
* tpl`Order ${event.record.number} held: ${fraud.output.reason}`
|
|
890
|
+
*/
|
|
891
|
+
declare function tpl(strings: TemplateStringsArray, ...values: ReadonlyArray<RefTo<unknown> | ConditionScalar>): AutomationTemplate;
|
|
892
|
+
/** Every test holds. */
|
|
893
|
+
declare function and(...conditions: readonly ConditionTree[]): ConditionTree;
|
|
894
|
+
declare function and(...conditions: readonly AutomationWhere[]): AutomationWhere;
|
|
895
|
+
/** Some test holds. */
|
|
896
|
+
declare function or(...conditions: readonly ConditionTree[]): ConditionTree;
|
|
897
|
+
declare function or(...conditions: readonly AutomationWhere[]): AutomationWhere;
|
|
898
|
+
/** The test does not hold. */
|
|
899
|
+
declare function not(condition: ConditionTree): ConditionTree;
|
|
900
|
+
declare function not(condition: AutomationWhere): AutomationWhere;
|
|
901
|
+
/** Where a trigger's event type rides, for the compiler alone. */
|
|
902
|
+
declare const EVENT_TYPE: unique symbol;
|
|
903
|
+
/** A trigger, as `on` takes it: its data, and the type of the event it hands `steps`. */
|
|
904
|
+
interface AutomationTriggerHandle<E> {
|
|
905
|
+
readonly trigger: AutomationTrigger;
|
|
906
|
+
readonly [EVENT_TYPE]?: E;
|
|
907
|
+
}
|
|
908
|
+
/** The Record type names a schema declares, or any name without one. */
|
|
909
|
+
type RecordTypeNameIn$1<S> = S extends AnySchemaDefinition ? SchemaRecordTypeNames<S> : string;
|
|
910
|
+
type ContentTypeNameIn<S> = S extends AnySchemaDefinition ? SchemaContentTypeNames<S> : string;
|
|
911
|
+
/** An entry's values, of one content type, typed by the registered schema when there is one. */
|
|
912
|
+
type EntryValuesIn<S, K> = S extends AnySchemaDefinition ? Omit<SchemaEntry<S, K & SchemaContentTypeNames<S>>, '_metadata'> : Record<string, unknown>;
|
|
913
|
+
/** A Record of one Record type, typed by the registered schema when there is one. */
|
|
914
|
+
type RecordIn$1<S, K> = S extends AnySchemaDefinition ? InferRecordType<NonNullable<S['recordTypes']>[K & keyof NonNullable<S['recordTypes']>]> : BaseRecord;
|
|
915
|
+
/** A Record event: the Record as the write left it, and the names of the fields it changed. */
|
|
916
|
+
interface RecordEvent<R> {
|
|
917
|
+
readonly record: R;
|
|
918
|
+
readonly changed: readonly string[];
|
|
919
|
+
}
|
|
920
|
+
/** An entry event: the entry as the write left it, and the names of the fields it changed. */
|
|
921
|
+
interface EntryEvent {
|
|
922
|
+
readonly entry: unknown;
|
|
923
|
+
readonly changed: readonly string[];
|
|
924
|
+
}
|
|
925
|
+
/** One field's `where`: its value, or the list grammar's operators and `becomes`. */
|
|
926
|
+
type FieldWhere<T> = ComparedWith<T> | ({
|
|
927
|
+
readonly becomes?: ComparedWith<T>;
|
|
928
|
+
} & {
|
|
929
|
+
readonly [Op in 'eq' | 'ne' | 'gt' | 'gte' | 'lt' | 'lte']?: ComparedWith<T>;
|
|
930
|
+
} & {
|
|
931
|
+
readonly in?: ReadonlyArray<ComparedWith<T>>;
|
|
932
|
+
readonly nin?: ReadonlyArray<ComparedWith<T>>;
|
|
933
|
+
readonly exists?: boolean;
|
|
934
|
+
});
|
|
935
|
+
/**
|
|
936
|
+
* A trigger's `where`: field by field over the item — `{ status: { becomes: 'paid' } }`, every field
|
|
937
|
+
* holding — or a condition over the event, written with `becomes` and the list grammar.
|
|
938
|
+
*/
|
|
939
|
+
type TriggerWhere<E, Item> = {
|
|
940
|
+
readonly [F in keyof Item & string]?: FieldWhere<Item[F]>;
|
|
941
|
+
} | ((event: Ref<E, true>) => AutomationWhere);
|
|
942
|
+
/** Where a type's item rides at run time: `records.order` says it is the Record type `order`. */
|
|
943
|
+
declare const ITEM: unique symbol;
|
|
944
|
+
/** Where one item by uid rides at run time: what `<type>.ref(uid)` returns. */
|
|
945
|
+
declare const ITEM_REF: unique symbol;
|
|
946
|
+
/** Where a type's or an item's value type rides, for the compiler alone. */
|
|
947
|
+
declare const ITEM_TYPE: unique symbol;
|
|
948
|
+
/** What a type, or one item of it, holds: a Record of `R`, or an entry with the values `V`. */
|
|
949
|
+
type ItemType<R = unknown, V = unknown> = {
|
|
950
|
+
readonly kind: 'record';
|
|
951
|
+
readonly record: R;
|
|
952
|
+
} | {
|
|
953
|
+
readonly kind: 'entry';
|
|
954
|
+
readonly values: V;
|
|
955
|
+
};
|
|
956
|
+
/** The type a Record or entry type's handle names, as data. */
|
|
957
|
+
type ItemTypeOf = {
|
|
958
|
+
readonly kind: 'record';
|
|
959
|
+
readonly recordType: string;
|
|
960
|
+
} | {
|
|
961
|
+
readonly kind: 'entry';
|
|
962
|
+
readonly contentType: string;
|
|
963
|
+
};
|
|
964
|
+
/**
|
|
965
|
+
* A uid: a reference to one — `event.record._metadata.id`, a reference field, an earlier step's
|
|
966
|
+
* output — or a uid written out.
|
|
967
|
+
*/
|
|
968
|
+
type UidRef = string | RefTo<string | null | undefined>;
|
|
969
|
+
/**
|
|
970
|
+
* One Record or entry, by its uid, typed by its type: `records.order.ref(event.record._metadata.id)`.
|
|
971
|
+
* `step.lookup` reads one, and a run may be declared to be about one (REL-4152).
|
|
972
|
+
*/
|
|
973
|
+
interface AutomationItemHandle<I extends ItemType = ItemType> {
|
|
974
|
+
readonly [ITEM_REF]: AutomationItemRef;
|
|
975
|
+
readonly [ITEM_TYPE]?: I;
|
|
976
|
+
}
|
|
977
|
+
/** The triggers of one Record type. */
|
|
978
|
+
interface RecordTriggers<R> {
|
|
979
|
+
/** A typed reference to one Record of this type, by its uid. */
|
|
980
|
+
ref(uid: UidRef): AutomationItemHandle<{
|
|
981
|
+
readonly kind: 'record';
|
|
982
|
+
readonly record: R;
|
|
983
|
+
}>;
|
|
984
|
+
readonly [ITEM]: ItemTypeOf;
|
|
985
|
+
readonly [ITEM_TYPE]?: {
|
|
986
|
+
readonly kind: 'record';
|
|
987
|
+
readonly record: R;
|
|
988
|
+
};
|
|
989
|
+
created(options?: {
|
|
990
|
+
where?: TriggerWhere<RecordEvent<R>, R>;
|
|
991
|
+
}): AutomationTriggerHandle<RecordEvent<R>>;
|
|
992
|
+
updated(options?: {
|
|
993
|
+
where?: TriggerWhere<RecordEvent<R>, R>;
|
|
994
|
+
}): AutomationTriggerHandle<RecordEvent<R>>;
|
|
995
|
+
deleted(options?: {
|
|
996
|
+
where?: TriggerWhere<RecordEvent<R>, R>;
|
|
997
|
+
}): AutomationTriggerHandle<RecordEvent<R>>;
|
|
998
|
+
expired(options?: {
|
|
999
|
+
where?: TriggerWhere<RecordEvent<R>, R>;
|
|
1000
|
+
}): AutomationTriggerHandle<RecordEvent<R>>;
|
|
1001
|
+
}
|
|
1002
|
+
/** The triggers of one content type, whose entries hold the values `V`. */
|
|
1003
|
+
type EntryTriggers<V = Record<string, unknown>> = {
|
|
1004
|
+
readonly [A in AutomationEntryAction]: (options?: {
|
|
1005
|
+
where?: TriggerWhere<EntryEvent, Record<string, unknown>>;
|
|
1006
|
+
}) => AutomationTriggerHandle<EntryEvent>;
|
|
1007
|
+
} & {
|
|
1008
|
+
/** A typed reference to one entry of this type, by its uid. */
|
|
1009
|
+
ref(uid: UidRef): AutomationItemHandle<{
|
|
1010
|
+
readonly kind: 'entry';
|
|
1011
|
+
readonly values: V;
|
|
1012
|
+
}>;
|
|
1013
|
+
readonly [ITEM]: ItemTypeOf;
|
|
1014
|
+
readonly [ITEM_TYPE]?: {
|
|
1015
|
+
readonly kind: 'entry';
|
|
1016
|
+
readonly values: V;
|
|
1017
|
+
};
|
|
1018
|
+
};
|
|
1019
|
+
/** Each Record type's triggers, by name: typed by the registered schema, any name without one. */
|
|
1020
|
+
type RecordTriggerTable = string extends RecordTypeNameIn$1<RegisteredSchema> ? {
|
|
1021
|
+
readonly [recordType: string]: any;
|
|
1022
|
+
} : {
|
|
1023
|
+
readonly [K in RecordTypeNameIn$1<RegisteredSchema>]: RecordTriggers<RecordIn$1<RegisteredSchema, K>>;
|
|
1024
|
+
};
|
|
1025
|
+
/** Each content type's triggers, by name. */
|
|
1026
|
+
type EntryTriggerTable = string extends ContentTypeNameIn<RegisteredSchema> ? {
|
|
1027
|
+
readonly [contentType: string]: any;
|
|
1028
|
+
} : {
|
|
1029
|
+
readonly [K in ContentTypeNameIn<RegisteredSchema>]: EntryTriggers<EntryValuesIn<RegisteredSchema, K>>;
|
|
1030
|
+
};
|
|
1031
|
+
/**
|
|
1032
|
+
* `records.<type>.created | updated | deleted | expired`: a Record event of one Record type.
|
|
1033
|
+
* `records.<type>` itself names the type to `step.lookup`, and `records.<type>.ref(uid)` one Record.
|
|
1034
|
+
*/
|
|
1035
|
+
declare const records: RecordTriggerTable;
|
|
1036
|
+
/**
|
|
1037
|
+
* `entries.<type>.created | updated | published | …`: an entry event of one content type.
|
|
1038
|
+
* `entries.<type>` itself names the type to `step.lookup`, and `entries.<type>.ref(uid)` one entry.
|
|
1039
|
+
*/
|
|
1040
|
+
declare const entries: EntryTriggerTable;
|
|
1041
|
+
/**
|
|
1042
|
+
* What a run started by an entry of any content type reads under `event` (REL-3861): what every
|
|
1043
|
+
* entry event holds whatever its type — the entry's uid and content type, and the names of the
|
|
1044
|
+
* fields the write changed. No field of the entry: no one content type declares it.
|
|
1045
|
+
*/
|
|
1046
|
+
interface AnyEntryEvent {
|
|
1047
|
+
readonly entry: {
|
|
1048
|
+
readonly _metadata: {
|
|
1049
|
+
readonly id: string;
|
|
1050
|
+
readonly type: string;
|
|
1051
|
+
};
|
|
1052
|
+
};
|
|
1053
|
+
readonly changed: readonly string[];
|
|
1054
|
+
}
|
|
1055
|
+
/** The triggers of an entry of any content type, one per entry action. */
|
|
1056
|
+
type AnyEntryTriggers = {
|
|
1057
|
+
readonly [A in AutomationEntryAction]: (options?: {
|
|
1058
|
+
readonly where?: (event: Ref<AnyEntryEvent>) => ConditionTree;
|
|
1059
|
+
}) => AutomationTriggerHandle<AnyEntryEvent>;
|
|
1060
|
+
};
|
|
1061
|
+
/**
|
|
1062
|
+
* `anyEntry.created | updated | published | …`: an entry event of any content type — what a site
|
|
1063
|
+
* that revalidates every page it renders listens to. `where` is a function of the event, which holds
|
|
1064
|
+
* the entry's `_metadata.id` and `_metadata.type` and `changed` alone, and no `becomes`:
|
|
1065
|
+
* `anyEntry.published({ where: event => event.entry._metadata.type.in(['post']) })`.
|
|
1066
|
+
*/
|
|
1067
|
+
declare const anyEntry: AnyEntryTriggers;
|
|
1068
|
+
/** A clock: `schedule.cron('0 8 * * 1', { timezone: 'Europe/Berlin' })`, on Production only. */
|
|
1069
|
+
declare const schedule: {
|
|
1070
|
+
cron(cron: string, options: {
|
|
1071
|
+
readonly timezone: string;
|
|
1072
|
+
}): AutomationTriggerHandle<Record<string, never>>;
|
|
1073
|
+
};
|
|
1074
|
+
/**
|
|
1075
|
+
* What a run started by another run's failure reads under `event` (REL-4152): the failed run, its
|
|
1076
|
+
* Automation and Branch, what it was about, the step it failed at, the error, and the event that set
|
|
1077
|
+
* it off by type and id. Never that event's body: a Record's values stay on the failed run.
|
|
1078
|
+
*/
|
|
1079
|
+
interface RunFailedEvent {
|
|
1080
|
+
readonly runId: string;
|
|
1081
|
+
/** The Automation whose run failed. */
|
|
1082
|
+
readonly automation: string;
|
|
1083
|
+
readonly branchId: string;
|
|
1084
|
+
/** The Record or entry the failed run was about — and so the one this run is about. */
|
|
1085
|
+
readonly about: {
|
|
1086
|
+
readonly kind: 'record' | 'entry';
|
|
1087
|
+
readonly type: string;
|
|
1088
|
+
readonly uid: string;
|
|
1089
|
+
} | null;
|
|
1090
|
+
readonly step: {
|
|
1091
|
+
readonly name: string;
|
|
1092
|
+
/** `function`, `agent`, `action`, `if` or `wait`. */
|
|
1093
|
+
readonly kind: string;
|
|
1094
|
+
readonly attempts: number;
|
|
1095
|
+
};
|
|
1096
|
+
readonly error: {
|
|
1097
|
+
readonly code: string;
|
|
1098
|
+
readonly message: string;
|
|
1099
|
+
};
|
|
1100
|
+
readonly trigger: {
|
|
1101
|
+
readonly eventType: string;
|
|
1102
|
+
readonly eventId: string;
|
|
1103
|
+
};
|
|
1104
|
+
}
|
|
1105
|
+
/** One value's `where` with no `becomes`: the value, or the list grammar's operators. */
|
|
1106
|
+
type ValueWhere<T> = ComparedWith<T> | ({
|
|
1107
|
+
readonly [Op in 'eq' | 'ne' | 'gt' | 'gte' | 'lt' | 'lte']?: ComparedWith<T>;
|
|
1108
|
+
} & {
|
|
1109
|
+
readonly in?: ReadonlyArray<ComparedWith<T>>;
|
|
1110
|
+
readonly nin?: ReadonlyArray<ComparedWith<T>>;
|
|
1111
|
+
readonly exists?: boolean;
|
|
1112
|
+
});
|
|
1113
|
+
/** Where each name a failed run's `where` shorthand filters on sits under `event`. */
|
|
1114
|
+
declare const RUN_FAILED_PATHS: {
|
|
1115
|
+
readonly automation: readonly ["automation"];
|
|
1116
|
+
readonly step: readonly ["step", "name"];
|
|
1117
|
+
readonly code: readonly ["error", "code"];
|
|
1118
|
+
};
|
|
1119
|
+
/**
|
|
1120
|
+
* A failed run's `where`: by its Automation, the step it failed at and the error's code —
|
|
1121
|
+
* `{ automation: 'order-paid', code: { in: ['send-refused'] } }`, every one holding — or a condition
|
|
1122
|
+
* over {@link RunFailedEvent}. No `becomes`: a failure changes no field.
|
|
1123
|
+
*/
|
|
1124
|
+
type RunFailedWhere = {
|
|
1125
|
+
readonly [K in keyof typeof RUN_FAILED_PATHS]?: ValueWhere<string>;
|
|
1126
|
+
} | ((event: Ref<RunFailedEvent>) => ConditionTree);
|
|
1127
|
+
/**
|
|
1128
|
+
* Another Automation's run failed: `automations.runFailed({ where })`, on the failed run's Branch.
|
|
1129
|
+
* An Automation never hears its own failure, and a run a failure started never announces its own.
|
|
1130
|
+
*/
|
|
1131
|
+
declare const automations: {
|
|
1132
|
+
runFailed(options?: {
|
|
1133
|
+
readonly where?: RunFailedWhere;
|
|
1134
|
+
}): AutomationTriggerHandle<RunFailedEvent>;
|
|
1135
|
+
};
|
|
1136
|
+
/**
|
|
1137
|
+
* What a run started by an Extension's Trigger reads under `event`: what the third party sent, as the
|
|
1138
|
+
* Trigger's payload fields declare it (`event.payload.data.object.id`).
|
|
1139
|
+
*/
|
|
1140
|
+
interface ExtensionTriggerEvent<P> {
|
|
1141
|
+
readonly payload: P;
|
|
1142
|
+
}
|
|
1143
|
+
/**
|
|
1144
|
+
* One of an Extension's Triggers, as its handle in `relyio/extensions.ts` builds it: the Extension by
|
|
1145
|
+
* slug, the Trigger by its slug in the catalog, and `where`, a plain condition over the event — no
|
|
1146
|
+
* `becomes`, since the third party's event names no field RelyIO saw change. Call the handle
|
|
1147
|
+
* (`stripe.paymentIntentSucceeded({ where })`) rather than this.
|
|
1148
|
+
*/
|
|
1149
|
+
declare function extensionTrigger<P>(extension: string, trigger: string, options?: {
|
|
1150
|
+
readonly where?: (event: Ref<ExtensionTriggerEvent<P>>) => ConditionTree;
|
|
1151
|
+
}): AutomationTriggerHandle<ExtensionTriggerEvent<P>>;
|
|
1152
|
+
/**
|
|
1153
|
+
* Every trigger, under one name: `on.records.order.updated(…)`, as `records.order.updated(…)`. An
|
|
1154
|
+
* Extension's Trigger is its handle's: `on: stripe.paymentIntentSucceeded()`.
|
|
1155
|
+
*/
|
|
1156
|
+
declare const on: {
|
|
1157
|
+
readonly records: {
|
|
1158
|
+
readonly [recordType: string]: any;
|
|
1159
|
+
};
|
|
1160
|
+
readonly entries: {
|
|
1161
|
+
readonly [contentType: string]: any;
|
|
1162
|
+
};
|
|
1163
|
+
readonly anyEntry: AnyEntryTriggers;
|
|
1164
|
+
readonly schedule: {
|
|
1165
|
+
cron(cron: string, options: {
|
|
1166
|
+
readonly timezone: string;
|
|
1167
|
+
}): AutomationTriggerHandle<Record<string, never>>;
|
|
1168
|
+
};
|
|
1169
|
+
readonly automations: {
|
|
1170
|
+
runFailed(options?: {
|
|
1171
|
+
readonly where?: RunFailedWhere;
|
|
1172
|
+
}): AutomationTriggerHandle<RunFailedEvent>;
|
|
1173
|
+
};
|
|
1174
|
+
};
|
|
1175
|
+
/** An Extension's Action, as `relyio/extensions.ts` hands one out (REL-3846): input and output typed. */
|
|
1176
|
+
interface AutomationAction<I = Record<string, unknown>, O = Record<string, unknown>, E extends string = string, N extends string = string> {
|
|
1177
|
+
readonly _tag: 'action';
|
|
1178
|
+
/** The Extension, by slug: a handle from `relyio/extensions.ts` carries it as a literal type. */
|
|
1179
|
+
readonly extension: E;
|
|
1180
|
+
/**
|
|
1181
|
+
* The Action, by its slug in the Extension catalog: what the step stores, and what a Function's
|
|
1182
|
+
* `actions.<extension>.<action>` names it by (REL-3856).
|
|
1183
|
+
*/
|
|
1184
|
+
readonly action: N;
|
|
1185
|
+
/** Never present at run time: the Action's input and output, as types. */
|
|
1186
|
+
readonly [REF_VALUE]?: {
|
|
1187
|
+
readonly input: I;
|
|
1188
|
+
readonly output: O;
|
|
1189
|
+
};
|
|
1190
|
+
}
|
|
1191
|
+
/** A step as `steps` builds it: its data, a Function step's Function still the imported value. */
|
|
1192
|
+
type AutomationStepDraft = (Omit<Extract<AutomationStep, {
|
|
1193
|
+
kind: 'function';
|
|
1194
|
+
}>, 'function'> & {
|
|
1195
|
+
readonly function: FunctionDefinition;
|
|
1196
|
+
}) | Exclude<AutomationStep, {
|
|
1197
|
+
kind: 'function' | 'if';
|
|
1198
|
+
}> | (Omit<Extract<AutomationStep, {
|
|
1199
|
+
kind: 'if';
|
|
1200
|
+
}>, 'then' | 'else'> & {
|
|
1201
|
+
readonly then: readonly AutomationStepDraft[];
|
|
1202
|
+
readonly else: readonly AutomationStepDraft[];
|
|
1203
|
+
});
|
|
1204
|
+
/** Where a step handle's data rides at run time. */
|
|
1205
|
+
declare const STEP: unique symbol;
|
|
1206
|
+
/** A step, as `steps` returns it and an If's branches list it. */
|
|
1207
|
+
interface StepHandle {
|
|
1208
|
+
readonly name: string;
|
|
1209
|
+
readonly [STEP]: AutomationStepDraft;
|
|
1210
|
+
}
|
|
1211
|
+
/** A step whose output later steps read: `<step>.output.<field>`. */
|
|
1212
|
+
interface StepWithOutput<O> extends StepHandle {
|
|
1213
|
+
readonly output: Ref<O>;
|
|
1214
|
+
}
|
|
1215
|
+
/** A Field set's values, as a Function's and an Agent step's output Fields declare them. */
|
|
1216
|
+
type ValuesOf$1<F extends KeyedMembers> = FieldValues<DeclaredFields<{
|
|
1217
|
+
readonly fields: readonly KeyedMembersOf<F>[];
|
|
1218
|
+
}>>;
|
|
1219
|
+
/** What `step.agent` takes. */
|
|
1220
|
+
interface AgentStepConfig<O extends KeyedMembers> {
|
|
1221
|
+
/** The agent, built-in or the project's, by id or slug; RelyIO's default when left out. */
|
|
1222
|
+
readonly agent?: string;
|
|
1223
|
+
readonly instructions: string | AutomationTemplate;
|
|
1224
|
+
/** What the agent is handed to read, by key. */
|
|
1225
|
+
readonly context?: {
|
|
1226
|
+
readonly [key: string]: unknown;
|
|
1227
|
+
};
|
|
1228
|
+
/** The Fields its answer must fill: a mismatch fails the step. */
|
|
1229
|
+
readonly output: O;
|
|
1230
|
+
}
|
|
1231
|
+
/**
|
|
1232
|
+
* What a proposing `step.agent` takes besides (REL-4154): the types it may propose a change to —
|
|
1233
|
+
* `entries.productPage`, `records.order`. It is offered ai-gateway's proposal tools for those types
|
|
1234
|
+
* alone and makes at most one proposal, which nothing applies until an Ask approves it.
|
|
1235
|
+
*/
|
|
1236
|
+
interface ProposingAgentStepConfig<O extends KeyedMembers> extends AgentStepConfig<O> {
|
|
1237
|
+
readonly propose: readonly [LookupType, ...LookupType[]];
|
|
1238
|
+
}
|
|
1239
|
+
/** The proposal a proposing Agent step made: what an Ask's `approve` names. */
|
|
1240
|
+
interface AutomationProposal {
|
|
1241
|
+
readonly id: string;
|
|
1242
|
+
/** The proposal's kind: `field_update`, `operation`, `record_update`, … */
|
|
1243
|
+
readonly kind: string;
|
|
1244
|
+
/** How many units it changes: entries, or Records. */
|
|
1245
|
+
readonly counts: {
|
|
1246
|
+
readonly units: number;
|
|
1247
|
+
};
|
|
1248
|
+
}
|
|
1249
|
+
/** A proposing Agent step's output: its Fields, and the proposal — `null` when it proposed none. */
|
|
1250
|
+
type ProposingOutput<V> = V & {
|
|
1251
|
+
readonly proposal: AutomationProposal | null;
|
|
1252
|
+
};
|
|
1253
|
+
/**
|
|
1254
|
+
* What a Look-up of a Record answers: `found`, and the Record as it stands now when it was. Read
|
|
1255
|
+
* `found` before the Record: an item deleted, never there, or a reference with no value is
|
|
1256
|
+
* `{ found: false }`.
|
|
1257
|
+
*/
|
|
1258
|
+
interface LookedUpRecord<R> {
|
|
1259
|
+
readonly found: boolean;
|
|
1260
|
+
readonly record?: R;
|
|
1261
|
+
}
|
|
1262
|
+
/** The locale codes the registered schema declares, or any code without one. */
|
|
1263
|
+
type LocaleCodeIn<S> = S extends AnySchemaDefinition ? SchemaLocaleCodes<S> : string;
|
|
1264
|
+
/** An entry as a Look-up reads it: its shared values, and each locale it admits with its own. */
|
|
1265
|
+
interface LookedUpEntryItem<V> {
|
|
1266
|
+
readonly _metadata: {
|
|
1267
|
+
readonly id: string;
|
|
1268
|
+
readonly type: string;
|
|
1269
|
+
};
|
|
1270
|
+
/** The values of the fields that are not localized. */
|
|
1271
|
+
readonly fields: Partial<V>;
|
|
1272
|
+
/** Each locale the state admits: its publication status and its localized values. */
|
|
1273
|
+
readonly locales: {
|
|
1274
|
+
readonly [L in LocaleCodeIn<RegisteredSchema>]?: {
|
|
1275
|
+
readonly status: 'published' | 'draft';
|
|
1276
|
+
readonly fields: Partial<V>;
|
|
1277
|
+
};
|
|
1278
|
+
};
|
|
1279
|
+
}
|
|
1280
|
+
/** What a Look-up of an entry answers: `found`, and the entry when it was. */
|
|
1281
|
+
interface LookedUpEntry<V> {
|
|
1282
|
+
readonly found: boolean;
|
|
1283
|
+
readonly entry?: LookedUpEntryItem<V>;
|
|
1284
|
+
}
|
|
1285
|
+
type IsAny<T> = 0 extends 1 & T ? true : false;
|
|
1286
|
+
/** What a Look-up of the type or item `H` answers. */
|
|
1287
|
+
type LookedUp<H> = IsAny<H> extends true ? {
|
|
1288
|
+
readonly found: boolean;
|
|
1289
|
+
readonly record?: any;
|
|
1290
|
+
readonly entry?: any;
|
|
1291
|
+
} : H extends {
|
|
1292
|
+
readonly [ITEM_TYPE]?: infer I;
|
|
1293
|
+
} ? I extends {
|
|
1294
|
+
readonly kind: 'record';
|
|
1295
|
+
readonly record: infer R;
|
|
1296
|
+
} ? LookedUpRecord<R> : I extends {
|
|
1297
|
+
readonly kind: 'entry';
|
|
1298
|
+
readonly values: infer V;
|
|
1299
|
+
} ? LookedUpEntry<V> : never : never;
|
|
1300
|
+
/** What a Look-up of `H` takes beside it: an entry's `state`; a Record has no draft. */
|
|
1301
|
+
type LookupOptions<H> = IsAny<H> extends true ? {
|
|
1302
|
+
readonly state?: AutomationLookupState;
|
|
1303
|
+
} : H extends {
|
|
1304
|
+
readonly [ITEM_TYPE]?: {
|
|
1305
|
+
readonly kind: 'entry';
|
|
1306
|
+
};
|
|
1307
|
+
} ? {
|
|
1308
|
+
/** `published` (the default): what readers see. `draft`: the editor's view. */
|
|
1309
|
+
readonly state?: AutomationLookupState;
|
|
1310
|
+
} : never;
|
|
1311
|
+
/** A Record or entry type, as `step.lookup` takes it: `records.order`, `entries.product`. */
|
|
1312
|
+
type LookupType = {
|
|
1313
|
+
readonly [ITEM]: ItemTypeOf;
|
|
1314
|
+
};
|
|
1315
|
+
/** What `step.ask` takes. */
|
|
1316
|
+
interface AskStepConfig<C extends string> {
|
|
1317
|
+
/**
|
|
1318
|
+
* The roles asked: `viewer`, `editor`, `admin` or a custom role the schema declares, as a
|
|
1319
|
+
* workflow stage's `allowedRoles`. Editors and Admins when left out. People only, never an agent.
|
|
1320
|
+
*/
|
|
1321
|
+
readonly to?: readonly string[];
|
|
1322
|
+
/** The question, rendered when the step runs: text, or {@link tpl} over the event and earlier outputs. */
|
|
1323
|
+
readonly question: string | AutomationTemplate;
|
|
1324
|
+
/** The answers a person may give. */
|
|
1325
|
+
readonly choices: readonly C[];
|
|
1326
|
+
/** How long the question stays open, up to 30 days; 7 days when left out. */
|
|
1327
|
+
readonly timeout?: AutomationDuration;
|
|
1328
|
+
/** The answer the step continues with once the timeout falls; without one, the step fails then. */
|
|
1329
|
+
readonly onTimeout?: NoInfer<C>;
|
|
1330
|
+
/** The answer a pull request's Preview run and an editor's test give at once; the first choice by default. */
|
|
1331
|
+
readonly sample?: NoInfer<C>;
|
|
1332
|
+
}
|
|
1333
|
+
/** What an Ask answers later steps: the choice made, and by whom, or that the timeout fell. */
|
|
1334
|
+
interface AskOutput<C extends string> {
|
|
1335
|
+
readonly answer: C;
|
|
1336
|
+
/** What the person wrote beside the choice, at most 2,000 characters. */
|
|
1337
|
+
readonly note?: string;
|
|
1338
|
+
/** The user who answered; absent on a timeout and a Preview's sample. */
|
|
1339
|
+
readonly answeredBy?: string;
|
|
1340
|
+
/** When, as ISO-8601; absent on a timeout and a Preview's sample. */
|
|
1341
|
+
readonly answeredAt?: string;
|
|
1342
|
+
/** Whether the step continued with `onTimeout` because nobody answered in time. */
|
|
1343
|
+
readonly timedOut: boolean;
|
|
1344
|
+
}
|
|
1345
|
+
/** An approving Ask's choices, in order: a Preview's sample is the first. */
|
|
1346
|
+
declare const APPROVE_CHOICES: readonly ["approve", "reject"];
|
|
1347
|
+
type ApproveChoice = (typeof APPROVE_CHOICES)[number];
|
|
1348
|
+
/**
|
|
1349
|
+
* What an approving `step.ask` takes (REL-4154): `approve` names an earlier Agent step's proposal,
|
|
1350
|
+
* and the choices are `approve` and `reject`. Only members holding `to`'s roles and the authority
|
|
1351
|
+
* the proposal's apply needs are asked or may answer. A timeout never approves.
|
|
1352
|
+
*/
|
|
1353
|
+
interface ApproveStepConfig {
|
|
1354
|
+
readonly to?: readonly string[];
|
|
1355
|
+
readonly question: string | AutomationTemplate;
|
|
1356
|
+
/** The proposal: `<agent step>.output.proposal`. */
|
|
1357
|
+
readonly approve: Ref<AutomationProposal | null>;
|
|
1358
|
+
readonly timeout?: AutomationDuration;
|
|
1359
|
+
/** What the step continues with once the timeout falls: only ever `reject`. */
|
|
1360
|
+
readonly onTimeout?: 'reject';
|
|
1361
|
+
/** What a Preview answers at once: `approve` by default. */
|
|
1362
|
+
readonly sample?: ApproveChoice;
|
|
1363
|
+
}
|
|
1364
|
+
/** What became of an approved — or not — proposal, by its units. */
|
|
1365
|
+
interface ApproveCounts {
|
|
1366
|
+
readonly applied: number;
|
|
1367
|
+
readonly stale: number;
|
|
1368
|
+
readonly failed: number;
|
|
1369
|
+
}
|
|
1370
|
+
/**
|
|
1371
|
+
* An approving Ask's output: the answer, and what became of the proposal — `applied`; `rejected`,
|
|
1372
|
+
* answered so or timed out; `stale`, a Record that moved since the agent read it; or `failed`.
|
|
1373
|
+
*/
|
|
1374
|
+
interface ApproveOutput extends AskOutput<ApproveChoice> {
|
|
1375
|
+
readonly outcome: 'applied' | 'rejected' | 'stale' | 'failed';
|
|
1376
|
+
readonly counts: ApproveCounts;
|
|
1377
|
+
}
|
|
1378
|
+
/** What `steps` is handed to build steps with. */
|
|
1379
|
+
interface StepBuilder {
|
|
1380
|
+
/** Run one of the project's Functions, taken by import, with its input. */
|
|
1381
|
+
fn<F extends FunctionDefinition>(name: string, fn: F, input: StepInput<FunctionInputOf<F>>): StepWithOutput<FunctionOutputOf<F>>;
|
|
1382
|
+
/**
|
|
1383
|
+
* Ask an agent to read, answer the declared Fields and propose one change to the types `propose`
|
|
1384
|
+
* names. Nothing it proposes is written until an Ask approves it (`step.ask(…, { approve })`).
|
|
1385
|
+
*/
|
|
1386
|
+
agent<const O extends KeyedMembers>(name: string, config: ProposingAgentStepConfig<O>): StepWithOutput<ProposingOutput<ValuesOf$1<O>>>;
|
|
1387
|
+
/** Ask an agent to read and answer the declared Fields. It reads; it never writes or sends. */
|
|
1388
|
+
agent<const O extends KeyedMembers>(name: string, config: AgentStepConfig<O>): StepWithOutput<ValuesOf$1<O>>;
|
|
1389
|
+
/** Send through an Extension's Action, from the events worker; Recorded in a Preview without its Connection. */
|
|
1390
|
+
action<I, O>(name: string, action: AutomationAction<I, O>, input: StepInput<I>): StepWithOutput<O>;
|
|
1391
|
+
/**
|
|
1392
|
+
* Read one Record or entry again, as it stands now, by its uid — `found`, or not: an item deleted,
|
|
1393
|
+
* never there, or a reference with no value is `{ found: false }`, never a failed step. An entry
|
|
1394
|
+
* reads `published` by default, or the editor's view with `{ state: 'draft' }`. One item, never a
|
|
1395
|
+
* query; unmetered.
|
|
1396
|
+
*
|
|
1397
|
+
* @example
|
|
1398
|
+
* const recheck = step.lookup('recheck', records.order, event.record._metadata.id)
|
|
1399
|
+
* step.if('still-unpaid', recheck.output.record.status.ne('paid'), { then: [remind] })
|
|
1400
|
+
*/
|
|
1401
|
+
lookup<H extends LookupType>(name: string, type: H, uid: UidRef, options?: LookupOptions<H>): StepWithOutput<LookedUp<H>>;
|
|
1402
|
+
/** Read one item, named by `<type>.ref(uid)`, again as it stands now. */
|
|
1403
|
+
lookup<H extends AutomationItemHandle>(name: string, item: H, options?: LookupOptions<H>): StepWithOutput<LookedUp<H>>;
|
|
1404
|
+
/** Then one step list or the other, by a condition over the event and earlier outputs. */
|
|
1405
|
+
if(name: string, condition: ConditionTree, branches: {
|
|
1406
|
+
readonly then?: readonly StepHandle[];
|
|
1407
|
+
readonly else?: readonly StepHandle[];
|
|
1408
|
+
}): StepHandle;
|
|
1409
|
+
/** Hold the run, up to 30 days: `'90m'`, `'2h'`, `'3d'`. */
|
|
1410
|
+
wait(name: string, duration: AutomationDuration): StepHandle;
|
|
1411
|
+
/**
|
|
1412
|
+
* Ask the people holding `to`'s roles, through the Studio inbox and an email, and continue with
|
|
1413
|
+
* the first answer — one of `choices` — or with `onTimeout` once `timeout` falls.
|
|
1414
|
+
*/
|
|
1415
|
+
ask<const C extends string>(name: string, config: AskStepConfig<C>): StepWithOutput<AskOutput<C>>;
|
|
1416
|
+
/**
|
|
1417
|
+
* Ask whether to apply an Agent step's proposal: `approve` applies it through ai-gateway's apply
|
|
1418
|
+
* path as the person who answered, and the run continues once it is applied, with the outcome.
|
|
1419
|
+
*
|
|
1420
|
+
* @example
|
|
1421
|
+
* const approval = step.ask('approve', { to: ['editor'], question: 'Publish the page?', approve: draft.output.proposal, onTimeout: 'reject' })
|
|
1422
|
+
* step.if('moved', approval.output.outcome.eq('stale'), { then: [tellOps] })
|
|
1423
|
+
*/
|
|
1424
|
+
ask(name: string, config: ApproveStepConfig): StepWithOutput<ApproveOutput>;
|
|
1425
|
+
}
|
|
1426
|
+
/** What `defineAutomation` takes. */
|
|
1427
|
+
interface AutomationConfig<E> {
|
|
1428
|
+
readonly description?: string;
|
|
1429
|
+
/**
|
|
1430
|
+
* The trigger: `records.<type>.<action>(…)`, `entries.<type>.<action>(…)`, `anyEntry.<action>(…)`,
|
|
1431
|
+
* `schedule.cron(…)`,
|
|
1432
|
+
* `automations.runFailed(…)`, or an Extension's Trigger from `relyio/extensions.ts`.
|
|
1433
|
+
*/
|
|
1434
|
+
readonly on: AutomationTriggerHandle<E>;
|
|
1435
|
+
/**
|
|
1436
|
+
* What each run is about, so the runs about one order are found across Automations: one Record or
|
|
1437
|
+
* entry, by a uid the event names — `({ event }) =>
|
|
1438
|
+
* records.order.ref(event.payload.data.object.metadata.orderId)`. Left out, a run is about what its
|
|
1439
|
+
* trigger names: the Record or entry of a RelyIO event, the outside object of an Extension's
|
|
1440
|
+
* Trigger (Stripe's payment intent), the failed run's item, or nothing for a clock.
|
|
1441
|
+
*/
|
|
1442
|
+
readonly about?: (context: {
|
|
1443
|
+
readonly event: Ref<E>;
|
|
1444
|
+
}) => AutomationItemHandle;
|
|
1445
|
+
/**
|
|
1446
|
+
* Called once, when the Manifest loads, with a reference to the event and the step builder;
|
|
1447
|
+
* returns the steps in the order a run takes them.
|
|
1448
|
+
*/
|
|
1449
|
+
readonly steps: (context: {
|
|
1450
|
+
readonly event: Ref<E>;
|
|
1451
|
+
readonly step: StepBuilder;
|
|
1452
|
+
}) => readonly StepHandle[];
|
|
1453
|
+
}
|
|
1454
|
+
/**
|
|
1455
|
+
* What an Automation file default-exports: its definition as data, each Function step still naming
|
|
1456
|
+
* the Function it imported. The CLI names those by their files ({@link resolveAutomation}).
|
|
1457
|
+
*/
|
|
1458
|
+
interface AutomationFile {
|
|
1459
|
+
readonly _tag: 'automation';
|
|
1460
|
+
readonly description?: string;
|
|
1461
|
+
readonly on: AutomationTrigger;
|
|
1462
|
+
readonly about?: AutomationItemRef;
|
|
1463
|
+
readonly steps: readonly AutomationStepDraft[];
|
|
1464
|
+
}
|
|
1465
|
+
/**
|
|
1466
|
+
* Declare an Automation. The file's name is its name; `relyio deploy` reads it into the Manifest,
|
|
1467
|
+
* plans it and ships it with the rest.
|
|
1468
|
+
*/
|
|
1469
|
+
declare function defineAutomation<E>(config: AutomationConfig<E>): AutomationFile;
|
|
1470
|
+
/** Whether `value` is what `defineAutomation` returns: how the CLI reads an Automation file. */
|
|
1471
|
+
declare function isAutomationFile(value: unknown): value is AutomationFile;
|
|
1472
|
+
/**
|
|
1473
|
+
* The Automation as the Manifest carries it: each Function step's Function named by `nameOf` — the
|
|
1474
|
+
* CLI's, which knows which file under `relyio/functions/` exported it. A Function no file exported
|
|
1475
|
+
* is refused by name.
|
|
1476
|
+
*/
|
|
1477
|
+
declare function resolveAutomation(file: AutomationFile, nameOf: (fn: FunctionDefinition) => string | undefined): AutomationDefinition;
|
|
1478
|
+
|
|
1479
|
+
/**
|
|
1480
|
+
* `@relyio/sdk/extensions` — the Extensions a Project uses (spec 3, REL-3846; ADR 0019).
|
|
1481
|
+
*
|
|
1482
|
+
* One file, `relyio/extensions.ts`, names each Extension the Project uses and its settings — set once
|
|
1483
|
+
* for the Project, never per Environment — and hands out a typed handle for each. An Automation
|
|
1484
|
+
* imports the handle: its Triggers start an Automation (`on: vercel.deploymentSucceeded()`), and its
|
|
1485
|
+
* Actions are what `step.action` sends through, their input and output typed by the Extension.
|
|
1486
|
+
*
|
|
1487
|
+
* @example
|
|
1488
|
+
* // relyio/extensions.ts
|
|
1489
|
+
* import { defineExtensions } from '@relyio/sdk/extensions'
|
|
1490
|
+
*
|
|
1491
|
+
* const extensions = defineExtensions({
|
|
1492
|
+
* slack: { defaultChannel: '#orders' },
|
|
1493
|
+
* vercel: {},
|
|
1494
|
+
* })
|
|
1495
|
+
* export const { slack, vercel } = extensions
|
|
1496
|
+
* export default extensions
|
|
1497
|
+
*
|
|
1498
|
+
* **Never a secret here.** A Connection — an API key, a signing secret, a webhook URL — is made in
|
|
1499
|
+
* the Dashboard, once per Environment, and sealed. A setting is text anyone reading the repository
|
|
1500
|
+
* may see. Declaring an Extension connects nothing: `relyio plan` says which still need connecting
|
|
1501
|
+
* in Production, and which Automations will deploy paused there until they are. Preview never pauses:
|
|
1502
|
+
* an Action with no Preview Connection is Recorded with its sample result.
|
|
1503
|
+
*
|
|
1504
|
+
* **Typed.** Each Extension's settings, Actions and Triggers are RelyIO's own declaration of it
|
|
1505
|
+
* (`@relyio/api-contract`'s `EXTENSION_SURFACES`, which the Extension catalog delivers): an Extension
|
|
1506
|
+
* RelyIO does not have, a setting it does not declare, or a value its setting does not take — a
|
|
1507
|
+
* Slack channel without its `#` — is a type error, and the plan refuses it again for a Manifest read
|
|
1508
|
+
* as data.
|
|
1509
|
+
*/
|
|
1510
|
+
|
|
1511
|
+
/** One Extension a Project uses: its settings, by key. */
|
|
1512
|
+
interface ExtensionUse {
|
|
1513
|
+
readonly settings: Readonly<Record<string, string>>;
|
|
1514
|
+
}
|
|
1515
|
+
/** What `relyio/extensions.ts` declares, as data: every Extension the Project uses, by slug. */
|
|
1516
|
+
type ExtensionsDeclaration = Readonly<Record<string, ExtensionUse>>;
|
|
1517
|
+
/** Every Extension a Project may declare, and what its code may name of each. */
|
|
1518
|
+
type Surfaces = ExtensionSurfaces;
|
|
1519
|
+
/** The value one setting takes: one of its options, text starting with its prefix, or text. */
|
|
1520
|
+
type SettingValue<F extends ExtensionSettingField> = F extends {
|
|
1521
|
+
readonly type: 'select';
|
|
1522
|
+
readonly options: ReadonlyArray<infer Option>;
|
|
1523
|
+
} ? Option : F extends {
|
|
1524
|
+
readonly type: 'text';
|
|
1525
|
+
readonly prefix: infer Prefix extends string;
|
|
1526
|
+
} ? `${Prefix}${string}` : string;
|
|
1527
|
+
type Flatten<T> = {
|
|
1528
|
+
readonly [K in keyof T]: T[K];
|
|
1529
|
+
};
|
|
1530
|
+
/** The settings an Extension takes: its required ones required, the rest optional, nothing else. */
|
|
1531
|
+
type SettingsFrom<F extends ExtensionSettingField> = Flatten<{
|
|
1532
|
+
readonly [Field in F as Field extends {
|
|
1533
|
+
readonly required: true;
|
|
1534
|
+
} ? Field['key'] : never]: SettingValue<Field>;
|
|
1535
|
+
} & {
|
|
1536
|
+
readonly [Field in F as Field extends {
|
|
1537
|
+
readonly required: true;
|
|
1538
|
+
} ? never : Field['key']]?: SettingValue<Field>;
|
|
1539
|
+
}>;
|
|
1540
|
+
/**
|
|
1541
|
+
* The settings `defineExtensions` takes for Extension `S`. An Extension with none takes `{}` and
|
|
1542
|
+
* nothing in it.
|
|
1543
|
+
*/
|
|
1544
|
+
type ExtensionSettings<S extends keyof Surfaces> = [
|
|
1545
|
+
Surfaces[S]['settings'][number]
|
|
1546
|
+
] extends [never] ? Readonly<Record<string, never>> : SettingsFrom<Surfaces[S]['settings'][number]>;
|
|
1547
|
+
/** What `defineExtensions` takes: each Extension the Project uses, by slug, with its settings. */
|
|
1548
|
+
type ExtensionsConfig = {
|
|
1549
|
+
readonly [S in keyof Surfaces]?: ExtensionSettings<S>;
|
|
1550
|
+
};
|
|
1551
|
+
/** A value field's JSON type, as TypeScript reads it. */
|
|
1552
|
+
type JsonOf<T extends ExtensionValueField['type']> = T extends 'string' ? string : T extends 'number' ? number : T extends 'boolean' ? boolean : T extends 'array' ? readonly unknown[] : Readonly<Record<string, unknown>>;
|
|
1553
|
+
/** A dot-separated path's first segment. */
|
|
1554
|
+
type Head<P extends string> = P extends `${infer First}.${string}` ? First : P;
|
|
1555
|
+
/** The fields whose path starts with segment `K`. */
|
|
1556
|
+
type Under<F extends ExtensionValueField, K extends string> = F extends ExtensionValueField ? Head<F['path']> extends K ? F : never : never;
|
|
1557
|
+
/** The fields under `K`, their paths with `K.` taken off; never for the field at `K` itself. */
|
|
1558
|
+
type Rebased<F extends ExtensionValueField, K extends string> = F extends ExtensionValueField ? F['path'] extends `${K}.${infer Rest}` ? Omit<F, 'path'> & {
|
|
1559
|
+
readonly path: Rest;
|
|
1560
|
+
} : never : never;
|
|
1561
|
+
/** A field's value: its JSON type, and an object of strings for one declaring string values. */
|
|
1562
|
+
type FieldValueOf<F extends ExtensionValueField> = F extends {
|
|
1563
|
+
readonly values: 'string';
|
|
1564
|
+
} ? Readonly<Record<string, string>> : JsonOf<F['type']>;
|
|
1565
|
+
/** The value at `K`: the field there, or an object of the fields under it. */
|
|
1566
|
+
type ValueAt<F extends ExtensionValueField, K extends string> = [
|
|
1567
|
+
Extract<F, {
|
|
1568
|
+
readonly path: K;
|
|
1569
|
+
}>
|
|
1570
|
+
] extends [never] ? ValuesOf<Rebased<F, K>> : FieldValueOf<Extract<F, {
|
|
1571
|
+
readonly path: K;
|
|
1572
|
+
}>>;
|
|
1573
|
+
/**
|
|
1574
|
+
* Value fields as an object: `payload.data.object.id` a `string` at `payload.data.object.id`. A
|
|
1575
|
+
* segment is required when a field under it is.
|
|
1576
|
+
*/
|
|
1577
|
+
type ValuesOf<F extends ExtensionValueField> = Flatten<{
|
|
1578
|
+
readonly [K in Head<F['path']> as true extends Under<F, K>['required'] ? K : never]: ValueAt<F, K>;
|
|
1579
|
+
} & {
|
|
1580
|
+
readonly [K in Head<F['path']> as true extends Under<F, K>['required'] ? never : K]?: ValueAt<F, K>;
|
|
1581
|
+
}>;
|
|
1582
|
+
/** A Trigger's payload, as an object; one that declares nothing, `{}`. */
|
|
1583
|
+
type ValuesOfList<L extends readonly ExtensionValueField[]> = [L[number]] extends [never] ? Record<string, never> : ValuesOf<L[number]>;
|
|
1584
|
+
/** An action field's value, as TypeScript reads it (`@relyio/api-contract`'s `ACTION_FIELD_TYPES`). */
|
|
1585
|
+
type ActionValueOf<T extends ActionField['type']> = T extends 'text' | 'url' ? string : T extends 'number' ? number : T extends 'boolean' ? boolean : T extends 'headers' ? Readonly<Record<string, string>> : AutomationJson;
|
|
1586
|
+
/** An Action's input or output fields as an object: required ones required, the rest optional. */
|
|
1587
|
+
type ActionValuesOf<L extends readonly ActionField[]> = Flatten<{
|
|
1588
|
+
readonly [F in L[number] as F extends {
|
|
1589
|
+
readonly required: true;
|
|
1590
|
+
} ? F['key'] : never]: ActionValueOf<F['type']>;
|
|
1591
|
+
} & {
|
|
1592
|
+
readonly [F in L[number] as F extends {
|
|
1593
|
+
readonly required: true;
|
|
1594
|
+
} ? never : F['key']]?: ActionValueOf<F['type']>;
|
|
1595
|
+
}>;
|
|
1596
|
+
/** A Trigger's handle: call it for the Automation's `on`, with a `where` over its event if needed. */
|
|
1597
|
+
type ExtensionTriggerHandle<P> = (options?: {
|
|
1598
|
+
readonly where?: (event: Ref<ExtensionTriggerEvent<P>>) => ConditionTree;
|
|
1599
|
+
}) => AutomationTriggerHandle<ExtensionTriggerEvent<P>>;
|
|
1600
|
+
/**
|
|
1601
|
+
* The handle an Extension's declaration gives: each of its Actions and Triggers, by the name it gives
|
|
1602
|
+
* them, typed by their fields.
|
|
1603
|
+
*/
|
|
1604
|
+
type HandleOf<X extends ExtensionSurface, S extends string = string> = {
|
|
1605
|
+
readonly [A in keyof X['actions']]: X['actions'][A] extends {
|
|
1606
|
+
readonly action: infer N extends string;
|
|
1607
|
+
readonly step: {
|
|
1608
|
+
readonly input: infer I extends readonly ActionField[];
|
|
1609
|
+
readonly output: infer O extends readonly ActionField[];
|
|
1610
|
+
};
|
|
1611
|
+
} ? AutomationAction<ActionValuesOf<I>, ActionValuesOf<O>, S, N> : never;
|
|
1612
|
+
} & {
|
|
1613
|
+
readonly [T in keyof X['triggers']]: X['triggers'][T] extends {
|
|
1614
|
+
readonly payload: infer P extends readonly ExtensionValueField[];
|
|
1615
|
+
} ? ExtensionTriggerHandle<ValuesOfList<P>> : never;
|
|
1616
|
+
};
|
|
1617
|
+
/**
|
|
1618
|
+
* A contributed field kind's options (spec 4, REL-3894): its base reference's, without `to`, which
|
|
1619
|
+
* the kind names, and never `localized` — a user is no translation.
|
|
1620
|
+
*/
|
|
1621
|
+
type ContributedFieldOptions<TTo extends string, TMulti extends boolean, TRequired extends boolean> = Omit<ReferenceConfig<TTo, TMulti>, 'to'> & {
|
|
1622
|
+
readonly required?: TRequired;
|
|
1623
|
+
readonly to?: never;
|
|
1624
|
+
} & KeyedFieldOptions<never>;
|
|
1625
|
+
/**
|
|
1626
|
+
* A contributed field kind's builder — `clerk.userRef()`: a reference to the Extension's Managed
|
|
1627
|
+
* Record type, typed exactly as `fields.reference` types a reference to it, which the Studio picks through
|
|
1628
|
+
* the kind's picker. The schema, reads, writes and Policies treat it as that reference.
|
|
1629
|
+
*/
|
|
1630
|
+
type ContributedFieldKindBuilder<TTo extends string> = <TMulti extends boolean = false, TRequired extends boolean = false>(options?: ContributedFieldOptions<TTo, TMulti, TRequired>) => KeyedField<'reference', ReferenceConfig<TTo, TMulti> & {
|
|
1631
|
+
required?: TRequired;
|
|
1632
|
+
}>;
|
|
1633
|
+
/** An Extension's contributed field kinds, by the name its declaration gives them. */
|
|
1634
|
+
type FieldKindsOf<X extends ExtensionSurface> = {
|
|
1635
|
+
readonly [K in keyof X['fieldKinds']]: X['fieldKinds'][K] extends {
|
|
1636
|
+
readonly base: {
|
|
1637
|
+
readonly to: infer To extends string;
|
|
1638
|
+
};
|
|
1639
|
+
} ? ContributedFieldKindBuilder<To> : never;
|
|
1640
|
+
};
|
|
1641
|
+
/** An Extension's Managed Record types, by the name its declaration gives them (REL-3863). */
|
|
1642
|
+
type RecordTypesOf<X extends ExtensionSurface> = {
|
|
1643
|
+
readonly [K in keyof X['recordTypes']]: X['recordTypes'][K];
|
|
1644
|
+
};
|
|
1645
|
+
/** Extension `S`'s handle. */
|
|
1646
|
+
type ExtensionHandle<S extends keyof Surfaces> = HandleOf<Surfaces[S], S & string> & FieldKindsOf<Surfaces[S]> & RecordTypesOf<Surfaces[S]>;
|
|
1647
|
+
/** Where the declaration rides on what `defineExtensions` returns. */
|
|
1648
|
+
declare const DECLARED = "declared";
|
|
1649
|
+
/**
|
|
1650
|
+
* Where an {@link ExtensionsFile}'s type keeps the settings it was declared with, for
|
|
1651
|
+
* {@link ClaimsOf} to read. Type-level only: no value carries it.
|
|
1652
|
+
*/
|
|
1653
|
+
declare const DECLARED_SETTINGS: unique symbol;
|
|
1654
|
+
/**
|
|
1655
|
+
* What `relyio/extensions.ts` default-exports: the declaration the CLI reads into the Manifest, and
|
|
1656
|
+
* a handle for each Extension it declares.
|
|
1657
|
+
*/
|
|
1658
|
+
type ExtensionsFile<C> = ExtensionsFileData & {
|
|
1659
|
+
readonly [S in keyof C & keyof Surfaces]: ExtensionHandle<S>;
|
|
1660
|
+
} & {
|
|
1661
|
+
readonly [DECLARED_SETTINGS]?: C;
|
|
1662
|
+
};
|
|
1663
|
+
/** The setting an identity Extension declares its Claims in (`@relyio/api-contract`'s `IDENTITY_CLAIMS_SETTING`). */
|
|
1664
|
+
type ClaimsSetting = 'claims';
|
|
1665
|
+
/** `S` without the whitespace `String.prototype.trim` takes off either end. */
|
|
1666
|
+
type Trimmed<S extends string> = S extends `${' ' | '\n' | '\t' | '\r'}${infer Rest}` ? Trimmed<Rest> : S extends `${infer Rest}${' ' | '\n' | '\t' | '\r'}` ? Trimmed<Rest> : S;
|
|
1667
|
+
/**
|
|
1668
|
+
* The Claim names a `claims` setting declares, read as `claimNamesOf` reads it at deploy: split on
|
|
1669
|
+
* commas, each trimmed, empty ones dropped. Text TypeScript does not know (`string`) may name any.
|
|
1670
|
+
*/
|
|
1671
|
+
type ClaimNames<S extends string> = string extends S ? string : S extends `${infer First},${infer Rest}` ? ClaimNames<First> | ClaimNames<Rest> : Trimmed<S> extends '' ? never : Trimmed<S>;
|
|
1672
|
+
/** Every Claim name the Extensions in `C` declare: an identity Extension's `claims` setting. */
|
|
1673
|
+
type DeclaredClaimNames<C> = {
|
|
1674
|
+
readonly [S in keyof C]: C[S] extends {
|
|
1675
|
+
readonly [K in ClaimsSetting]: infer T extends string;
|
|
1676
|
+
} ? ClaimNames<T> : never;
|
|
1677
|
+
}[keyof C];
|
|
1678
|
+
/**
|
|
1679
|
+
* The Claims a Project relies on, typed from its `relyio/extensions.ts`: `ClaimsOf<typeof
|
|
1680
|
+
* extensions>` is an object with one optional key per Claim its identity Extension's `claims`
|
|
1681
|
+
* setting names — `claims: 'plan, role'` is `{ plan?: unknown; role?: unknown }`. A Claim named and
|
|
1682
|
+
* missing from a token is absent, and a setting is text, so each value is `unknown`: what a Claim may
|
|
1683
|
+
* hold is for the Policy that tests it to say.
|
|
1684
|
+
*
|
|
1685
|
+
* Type a Policy's `claims` root with it, so a misspelt Claim is a compile error:
|
|
1686
|
+
*
|
|
1687
|
+
* @example
|
|
1688
|
+
* import type { ClaimsOf } from '@relyio/sdk/extensions'
|
|
1689
|
+
* import { conditionRoot } from '@relyio/sdk/schema'
|
|
1690
|
+
* import extensions from './extensions'
|
|
1691
|
+
*
|
|
1692
|
+
* const claims = conditionRoot<ClaimsOf<typeof extensions>>('claims')
|
|
1693
|
+
* claims.plan.eq('paid') // ✓
|
|
1694
|
+
* claims.plna.eq('paid') // ✗ `plna` is no Claim the Project declares
|
|
1695
|
+
*/
|
|
1696
|
+
type ClaimsOf<F> = F extends {
|
|
1697
|
+
readonly [DECLARED_SETTINGS]?: infer C;
|
|
1698
|
+
} ? {
|
|
1699
|
+
readonly [N in DeclaredClaimNames<C>]?: unknown;
|
|
1700
|
+
} : never;
|
|
1701
|
+
/** `C`, refusing a key no Extension declares — at the top and in each Extension's settings. */
|
|
1702
|
+
type Exactly<C> = {
|
|
1703
|
+
readonly [S in keyof C]: S extends keyof Surfaces ? C[S] & {
|
|
1704
|
+
readonly [K in Exclude<keyof C[S], keyof ExtensionSettings<S>>]: never;
|
|
1705
|
+
} : never;
|
|
1706
|
+
};
|
|
1707
|
+
/**
|
|
1708
|
+
* Declare the Extensions the Project uses, with their settings, and take a typed handle for each.
|
|
1709
|
+
* The file is `relyio/extensions.ts`; `relyio deploy` reads its default export into the Manifest.
|
|
1710
|
+
*/
|
|
1711
|
+
declare function defineExtensions<const C extends ExtensionsConfig>(config: C & Exactly<C>): ExtensionsFile<C>;
|
|
1712
|
+
/** What the CLI reads of `relyio/extensions.ts`' default export, whatever it declares. */
|
|
1713
|
+
interface ExtensionsFileData {
|
|
1714
|
+
readonly _tag: 'extensions';
|
|
1715
|
+
readonly [DECLARED]: ExtensionsDeclaration;
|
|
1716
|
+
}
|
|
1717
|
+
/** Whether `value` is what `defineExtensions` returns: how the CLI reads `relyio/extensions.ts`. */
|
|
1718
|
+
declare function isExtensionsFile(value: unknown): value is ExtensionsFileData;
|
|
1719
|
+
/** The declaration as the Manifest carries it. */
|
|
1720
|
+
declare function extensionsDeclarationOf(file: ExtensionsFileData): ExtensionsDeclaration;
|
|
1721
|
+
|
|
1722
|
+
/**
|
|
1723
|
+
* The atomic Record batch (spec 1, REL-3774): `relyio.records.batch([...])` — an ordered list of
|
|
1724
|
+
* Record operations across Record types, run in one transaction, all or none.
|
|
1725
|
+
*
|
|
1726
|
+
* Each operation is `create`, `update`, `delete`, `increment` or `decrement`, and names its Record by
|
|
1727
|
+
* `id`, by `where` (the one Record of its type whose top-level fields have those values), or both —
|
|
1728
|
+
* `where` then a precondition. A refused operation rolls the whole batch back, and the refusal names
|
|
1729
|
+
* it: {@link refusedOperationOf} reads its index off the error. A counter moved past its bound is a
|
|
1730
|
+
* `ResourceConflictError` whose `blockage` is `counter-out-of-bounds`, naming the field, the bound and
|
|
1731
|
+
* the value it holds.
|
|
1732
|
+
*
|
|
1733
|
+
* Every batch is sent with an `Idempotency-Key` — yours, or one minted for the call — so a batch
|
|
1734
|
+
* retried after a lost answer is answered as done, never written twice.
|
|
1735
|
+
*/
|
|
1736
|
+
|
|
1737
|
+
/** A condition's value: what one top-level field holds, compared for equality. */
|
|
1738
|
+
type RecordConditionValue = string | number | boolean | null;
|
|
1739
|
+
/**
|
|
1740
|
+
* Which stored Record an operation lands on: by `id`, by `where`, or both. Every `where` is read
|
|
1741
|
+
* before the batch's first operation runs, so it names a Record stored before the batch — never one
|
|
1742
|
+
* an earlier operation of the same batch creates.
|
|
1743
|
+
*/
|
|
1744
|
+
type RecordBatchTarget = {
|
|
1745
|
+
id: string;
|
|
1746
|
+
where?: Record<string, RecordConditionValue>;
|
|
1747
|
+
} | {
|
|
1748
|
+
id?: undefined;
|
|
1749
|
+
where: Record<string, RecordConditionValue>;
|
|
1750
|
+
};
|
|
1751
|
+
/** One operation of a batch. `type` is a Record type the schema declares. */
|
|
1752
|
+
type RecordBatchOperation<TType extends string = string> = {
|
|
1753
|
+
op: 'create';
|
|
1754
|
+
type: TType;
|
|
1755
|
+
fields: Record<string, unknown>;
|
|
1756
|
+
} | ({
|
|
1757
|
+
op: 'update';
|
|
1758
|
+
type: TType;
|
|
1759
|
+
/** The fields to change; one given `null` is removed. A counter is never set. */
|
|
1760
|
+
fields: Record<string, unknown>;
|
|
1761
|
+
/** The revision the change was made on; absent, the update writes over the one stored. */
|
|
1762
|
+
revision?: number;
|
|
1763
|
+
} & RecordBatchTarget) | ({
|
|
1764
|
+
op: 'delete';
|
|
1765
|
+
type: TType;
|
|
1766
|
+
} & RecordBatchTarget) | ({
|
|
1767
|
+
op: 'increment' | 'decrement';
|
|
1768
|
+
type: TType;
|
|
1769
|
+
/** The counter. */
|
|
1770
|
+
field: string;
|
|
1771
|
+
/** A whole number above zero. */
|
|
1772
|
+
by: number;
|
|
1773
|
+
} & RecordBatchTarget);
|
|
1774
|
+
/** What a batch takes beside its operations. */
|
|
1775
|
+
interface RecordBatchOptions extends RecordCallOptions {
|
|
1776
|
+
/**
|
|
1777
|
+
* The key naming this batch. Sent again — by you, or by a retry — the batch is answered as the
|
|
1778
|
+
* first was and written once; with other operations it is refused. Minted for you when absent.
|
|
1779
|
+
*/
|
|
1780
|
+
idempotencyKey?: string;
|
|
1781
|
+
}
|
|
1782
|
+
/** What a committed batch answers: each operation, in order, and whether this is a replay. */
|
|
1783
|
+
type RecordBatchResult = components['schemas']['RecordBatchResult'];
|
|
1784
|
+
/** What one operation of a committed batch did. */
|
|
1785
|
+
type RecordBatchOperationResult = components['schemas']['RecordBatchOperationResult'];
|
|
1786
|
+
/**
|
|
1787
|
+
* The index, from 0, of the batch operation a refusal names: a `ResourceConflictError`'s
|
|
1788
|
+
* `blockage.facts.operation`, or the first violation under `operations[<index>]` of a
|
|
1789
|
+
* `ValidationError`. `undefined` for an error that names none — one about the whole batch, such as
|
|
1790
|
+
* an allowance, or no batch refusal at all.
|
|
1791
|
+
*/
|
|
1792
|
+
declare function refusedOperationOf(error: unknown): number | undefined;
|
|
1793
|
+
|
|
1794
|
+
/**
|
|
1795
|
+
* `@relyio/sdk/functions` — a Function, declared (spec 2, REL-3819; ADR 0021).
|
|
1796
|
+
*
|
|
1797
|
+
* One file per Function at `relyio/functions/<name>.ts`, default-exporting what
|
|
1798
|
+
* {@link defineFunction} returns; the file's name is the Function's. Everything but `run` is data —
|
|
1799
|
+
* its input and output Fields, its reach, its Secrets and its Ways in — which `relyio deploy` reads
|
|
1800
|
+
* into the Manifest and plans. `run` is the code: the CLI bundles the file and uploads the bundle.
|
|
1801
|
+
*
|
|
1802
|
+
* @example
|
|
1803
|
+
* // relyio/functions/checkout.ts
|
|
1804
|
+
* import { defineFunction, fields } from '@relyio/sdk/functions'
|
|
1805
|
+
*
|
|
1806
|
+
* export default defineFunction({
|
|
1807
|
+
* input: { cartId: fields.shortText({ required: true }) },
|
|
1808
|
+
* output: { orderId: fields.shortText({ required: true }) },
|
|
1809
|
+
* records: { read: ['cart', 'product'], write: ['order', 'inventory'] },
|
|
1810
|
+
* network: [{ host: 'api.stripe.com' }],
|
|
1811
|
+
* secrets: { STRIPE_SECRET_KEY: { hosts: ['api.stripe.com'] } },
|
|
1812
|
+
* route: { method: 'POST', path: '/checkout' },
|
|
1813
|
+
* async run({ input, records }) {
|
|
1814
|
+
* const cart = await records.cart.get(input.cartId)
|
|
1815
|
+
* // records.review here is a type error: checkout does not declare it
|
|
1816
|
+
* …
|
|
1817
|
+
* },
|
|
1818
|
+
* })
|
|
1819
|
+
*
|
|
1820
|
+
* **Typed from the schema.** Once `relyio generate-types` has registered the schema (`Register`), a
|
|
1821
|
+
* Record type the reach names must be one the schema declares, and `records.<type>` is typed by its
|
|
1822
|
+
* fields. A type the reach does not name is not on `records` at all.
|
|
1823
|
+
*
|
|
1824
|
+
* The capability object's runtime is the Functions runner's (spec 2, F2); this module declares its
|
|
1825
|
+
* types and the data `defineFunction` evaluates to.
|
|
1826
|
+
*/
|
|
1827
|
+
|
|
1828
|
+
/** Where a Function runs: a Dynamic Worker by default, a Container when it says so. */
|
|
1829
|
+
type FunctionRuntime = 'isolate' | 'container';
|
|
1830
|
+
/** A Container's instance type (Cloudflare's names): up to `standard-4`, 4 vCPU and 12 GiB. */
|
|
1831
|
+
type FunctionContainerSize = 'lite' | 'basic' | 'standard-1' | 'standard-2' | 'standard-3' | 'standard-4';
|
|
1832
|
+
/** A whole number of seconds or minutes: `'30s'`, `'5m'`. */
|
|
1833
|
+
type FunctionDuration = `${number}s` | `${number}m`;
|
|
1834
|
+
/** The methods a route answers. */
|
|
1835
|
+
type FunctionRouteMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
1836
|
+
/** A host a Function may call. `preview: 'send'`: a Preview branch's request to it is sent, not Recorded. */
|
|
1837
|
+
interface FunctionNetworkHost {
|
|
1838
|
+
readonly host: string;
|
|
1839
|
+
readonly preview?: 'send';
|
|
1840
|
+
}
|
|
1841
|
+
/**
|
|
1842
|
+
* An Extension's Action a Function may send through (spec 3, REL-3856), as the declaration carries
|
|
1843
|
+
* it: the Extension's and the Action's catalog slugs.
|
|
1844
|
+
*/
|
|
1845
|
+
interface FunctionActionReach {
|
|
1846
|
+
readonly extension: string;
|
|
1847
|
+
readonly action: string;
|
|
1848
|
+
}
|
|
1849
|
+
/**
|
|
1850
|
+
* An Action handle `reach.actions` takes: what `relyio/extensions.ts`' handles hand out
|
|
1851
|
+
* (`resend.send`), its Extension and Action slugs and its input and output carried as types.
|
|
1852
|
+
*/
|
|
1853
|
+
type FunctionActionHandle = AutomationAction<unknown, unknown>;
|
|
1854
|
+
/** A Secret a Function declares: the hosts it may be sent to. Its value is set per Environment. */
|
|
1855
|
+
interface FunctionSecretDeclaration {
|
|
1856
|
+
readonly hosts: readonly string[];
|
|
1857
|
+
}
|
|
1858
|
+
/** An HTTP route: static segments and `:params` — `'/orders/:id'`. */
|
|
1859
|
+
interface FunctionRoute {
|
|
1860
|
+
readonly method: FunctionRouteMethod;
|
|
1861
|
+
readonly path: string;
|
|
1862
|
+
/** Present: a Public route on the Project's host, called without a key, 60 calls a minute per address unless `perIp` says otherwise. */
|
|
1863
|
+
readonly public?: {
|
|
1864
|
+
readonly perIp?: number;
|
|
1865
|
+
/** A Secret this Function declares, holding the Turnstile secret key a call's token is verified with. */
|
|
1866
|
+
readonly turnstile?: string;
|
|
1867
|
+
};
|
|
1868
|
+
/** Browser origins allowed to call the route: `['https://shop.example.com']`. */
|
|
1869
|
+
readonly cors?: readonly string[];
|
|
1870
|
+
}
|
|
1871
|
+
/** An agent tool, offered in the AI Workspace and over MCP. */
|
|
1872
|
+
interface FunctionTool {
|
|
1873
|
+
readonly description: string;
|
|
1874
|
+
}
|
|
1875
|
+
/** A schedule, to the minute, on Production only: `{ cron: '0 8 * * 1', timezone: 'Europe/Berlin' }`. */
|
|
1876
|
+
interface FunctionSchedule {
|
|
1877
|
+
readonly cron: string;
|
|
1878
|
+
readonly timezone: string;
|
|
1879
|
+
}
|
|
1880
|
+
/**
|
|
1881
|
+
* A stored Field, as the content model compiles a keyed one. Plain data: what the Manifest carries
|
|
1882
|
+
* and `@relyio/manifest` validates.
|
|
1883
|
+
*/
|
|
1884
|
+
type FunctionFieldDeclaration = object;
|
|
1885
|
+
/**
|
|
1886
|
+
* What a Function declares besides its code, every default filled: the Functions area's data shape
|
|
1887
|
+
* (`@relyio/manifest`'s `FunctionDeclaration`, which the CLI's type test holds this to).
|
|
1888
|
+
*/
|
|
1889
|
+
interface FunctionDeclaration {
|
|
1890
|
+
readonly description?: string;
|
|
1891
|
+
readonly runtime: FunctionRuntime;
|
|
1892
|
+
readonly container?: {
|
|
1893
|
+
readonly size: FunctionContainerSize;
|
|
1894
|
+
};
|
|
1895
|
+
readonly limits?: {
|
|
1896
|
+
readonly cpu?: FunctionDuration;
|
|
1897
|
+
readonly duration?: FunctionDuration;
|
|
1898
|
+
};
|
|
1899
|
+
readonly input: readonly FunctionFieldDeclaration[];
|
|
1900
|
+
readonly output?: readonly FunctionFieldDeclaration[];
|
|
1901
|
+
readonly records: {
|
|
1902
|
+
readonly read: readonly string[];
|
|
1903
|
+
readonly write: readonly string[];
|
|
1904
|
+
};
|
|
1905
|
+
readonly content: 'published' | 'none';
|
|
1906
|
+
readonly network: readonly FunctionNetworkHost[];
|
|
1907
|
+
readonly secrets: Readonly<Record<string, FunctionSecretDeclaration>>;
|
|
1908
|
+
/** The Actions it may send (REL-3856); absent when it declares none. */
|
|
1909
|
+
readonly actions?: readonly FunctionActionReach[];
|
|
1910
|
+
/** Who besides a secret key may call its SDK Way in (spec 4, REL-3892); absent when undeclared. */
|
|
1911
|
+
readonly endUsers?: FunctionEndUsers;
|
|
1912
|
+
readonly route?: FunctionRoute;
|
|
1913
|
+
readonly tool?: FunctionTool;
|
|
1914
|
+
readonly schedule?: FunctionSchedule;
|
|
1915
|
+
}
|
|
1916
|
+
/**
|
|
1917
|
+
* The Record type names a schema declares, or any name without one. Generic over the schema on
|
|
1918
|
+
* purpose: written over `RegisteredSchema` directly, the conditional was resolved once, before the
|
|
1919
|
+
* program's `Register` augmentation was in view, and answered `string` (measured).
|
|
1920
|
+
*/
|
|
1921
|
+
type RecordTypeNameIn<S> = S extends AnySchemaDefinition ? SchemaRecordTypeNames<S> : string;
|
|
1922
|
+
/** The Record type names the registered schema declares, or any name before one is registered. */
|
|
1923
|
+
type FunctionRecordTypeName = RecordTypeNameIn<RegisteredSchema>;
|
|
1924
|
+
/** A Record type of a schema, by name. */
|
|
1925
|
+
type RecordTypeIn<S, K> = S extends AnySchemaDefinition ? NonNullable<S['recordTypes']>[K & keyof NonNullable<S['recordTypes']>] : never;
|
|
1926
|
+
/** A Record of one Record type as a read answers it, typed by the schema when there is one. */
|
|
1927
|
+
type RecordIn<S, K> = S extends AnySchemaDefinition ? InferRecordType<RecordTypeIn<S, K>> : BaseRecord;
|
|
1928
|
+
/** What a create of one Record type takes. */
|
|
1929
|
+
type WriteIn<S, K> = S extends AnySchemaDefinition ? RecordWriteFieldsOf<RecordTypeIn<S, K>> : Record<string, unknown>;
|
|
1930
|
+
/** What an update of one Record type takes: the create's values without its Counters. */
|
|
1931
|
+
type UpdateIn<S, K> = S extends AnySchemaDefinition ? RecordUpdateFieldsOf<RecordTypeIn<S, K>> : Record<string, unknown>;
|
|
1932
|
+
/** A Record of one Record type of the registered schema. */
|
|
1933
|
+
type RecordOf<K> = RecordIn<RegisteredSchema, K>;
|
|
1934
|
+
/**
|
|
1935
|
+
* A File a run sets (REL-3832), as `records.file(bytes, { name })` answers it: its name, media type
|
|
1936
|
+
* and size, and the bytes held beside it until the write naming it is sent. Set it as the value of
|
|
1937
|
+
* a File field in a create, an update or a batch; the run asks RelyIO for the upload, puts the bytes
|
|
1938
|
+
* and sends the write naming it, so a retried run names the File its first attempt stored.
|
|
1939
|
+
*/
|
|
1940
|
+
interface FunctionFile {
|
|
1941
|
+
readonly name: string;
|
|
1942
|
+
/** The media type: as given, else read off the name's extension, else `application/octet-stream`. */
|
|
1943
|
+
readonly type: string;
|
|
1944
|
+
/** In bytes. */
|
|
1945
|
+
readonly size: number;
|
|
1946
|
+
}
|
|
1947
|
+
/** What a write takes: a File field takes a {@link FunctionFile} where it takes an upload. */
|
|
1948
|
+
type WithFunctionFiles<T> = {
|
|
1949
|
+
[F in keyof T]: FileUpload extends NonNullable<T[F]> ? T[F] | FunctionFile : T[F];
|
|
1950
|
+
};
|
|
1951
|
+
/** A Counter move in a run: the Record by `id`, `where` or both, the Counter, and how far. */
|
|
1952
|
+
type FunctionCounterMove = RecordBatchTarget & {
|
|
1953
|
+
readonly field: string;
|
|
1954
|
+
/** A whole number above zero. */
|
|
1955
|
+
readonly by: number;
|
|
1956
|
+
};
|
|
1957
|
+
/**
|
|
1958
|
+
* What a run may do with a Record type it only reads (REL-3822), on the run's Dataset, through spec
|
|
1959
|
+
* 1's Record reader. `get` answers `null` for a Record not stored. A Record type the reach does not
|
|
1960
|
+
* name is refused at run time too, not only by these types.
|
|
1961
|
+
*/
|
|
1962
|
+
interface FunctionRecordReader<K> {
|
|
1963
|
+
get(id: string): Promise<RecordOf<K> | null>;
|
|
1964
|
+
list(options?: {
|
|
1965
|
+
/** Field conditions, in the grammar `relyio.records(type).list({ where })` takes. */
|
|
1966
|
+
where?: Record<string, unknown>;
|
|
1967
|
+
/** One key and its direction: `createdAt:desc` when absent. */
|
|
1968
|
+
sort?: `${string}:${'asc' | 'desc'}`;
|
|
1969
|
+
/** At most 100. */
|
|
1970
|
+
limit?: number;
|
|
1971
|
+
cursor?: string;
|
|
1972
|
+
}): Promise<{
|
|
1973
|
+
data: RecordOf<K>[];
|
|
1974
|
+
page: {
|
|
1975
|
+
nextCursor: string | null;
|
|
1976
|
+
hasMore: boolean;
|
|
1977
|
+
};
|
|
1978
|
+
}>;
|
|
1979
|
+
count(options?: {
|
|
1980
|
+
where?: Record<string, unknown>;
|
|
1981
|
+
}): Promise<number>;
|
|
1982
|
+
}
|
|
1983
|
+
/**
|
|
1984
|
+
* What a run may do with a Record type it writes: read it, and write it one operation at a time —
|
|
1985
|
+
* each sent as a batch of one, keyed as every batch of the run is, so a retried run writes it once.
|
|
1986
|
+
* A refusal throws {@link FunctionBatchRefused} naming operation 0.
|
|
1987
|
+
*/
|
|
1988
|
+
interface FunctionRecordWriter<K> extends FunctionRecordReader<K> {
|
|
1989
|
+
create(fields: WithFunctionFiles<WriteIn<RegisteredSchema, K>>): Promise<RecordOf<K>>;
|
|
1990
|
+
update(id: string, fields: {
|
|
1991
|
+
[F in keyof UpdateIn<RegisteredSchema, K>]?: WithFunctionFiles<UpdateIn<RegisteredSchema, K>>[F] | null;
|
|
1992
|
+
}, options?: {
|
|
1993
|
+
revision?: number;
|
|
1994
|
+
}): Promise<RecordOf<K>>;
|
|
1995
|
+
delete(id: string): Promise<void>;
|
|
1996
|
+
increment(move: FunctionCounterMove): Promise<RecordOf<K>>;
|
|
1997
|
+
decrement(move: FunctionCounterMove): Promise<RecordOf<K>>;
|
|
1998
|
+
}
|
|
1999
|
+
/**
|
|
2000
|
+
* The synchronous collector `records.batch` hands its callback: each call adds one operation, over
|
|
2001
|
+
* the Record types the Function writes, and nothing is read inside it. The batch is sent once the
|
|
2002
|
+
* callback returns — all or none. Each answers `undefined`, and the callback must too: one that
|
|
2003
|
+
* awaits returns a promise, which is a type error here and refused at run time.
|
|
2004
|
+
*/
|
|
2005
|
+
interface FunctionRecordBatch<W extends string> {
|
|
2006
|
+
create(type: W, fields: Record<string, unknown>): undefined;
|
|
2007
|
+
update(type: W, target: RecordBatchTarget & {
|
|
2008
|
+
fields: Record<string, unknown>;
|
|
2009
|
+
revision?: number;
|
|
2010
|
+
}): undefined;
|
|
2011
|
+
delete(type: W, target: RecordBatchTarget): undefined;
|
|
2012
|
+
increment(type: W, move: FunctionCounterMove): undefined;
|
|
2013
|
+
decrement(type: W, move: FunctionCounterMove): undefined;
|
|
2014
|
+
}
|
|
2015
|
+
/** What a committed batch answers: the Records it created, in order, and every operation's outcome. */
|
|
2016
|
+
type FunctionBatchResult = RecordBatchResult & {
|
|
2017
|
+
/** The Records its creates stored, in the order the batch sent them. */
|
|
2018
|
+
readonly created: BaseRecord[];
|
|
2019
|
+
};
|
|
2020
|
+
/**
|
|
2021
|
+
* What a refused batch throws in a run (REL-3822), a one-operation write's included: nothing of the
|
|
2022
|
+
* batch was written. `operation` is the refused operation's index from 0, `code` the API's code
|
|
2023
|
+
* (`RESOURCE_CONFLICT`, `CONTENT_INVALID`, `ACCESS_DENIED`, …), `reason` what it named — a
|
|
2024
|
+
* blockage's kind such as `counter-out-of-bounds`, or an issue's code — and `field` the Record field
|
|
2025
|
+
* it is about, when it names one: which line of a checkout ran out of stock.
|
|
2026
|
+
*/
|
|
2027
|
+
interface FunctionBatchRefused extends Error {
|
|
2028
|
+
readonly name: 'BatchRefused';
|
|
2029
|
+
readonly operation: number | null;
|
|
2030
|
+
readonly code: string;
|
|
2031
|
+
readonly reason: string | null;
|
|
2032
|
+
readonly field: string | null;
|
|
2033
|
+
readonly details: Readonly<Record<string, unknown>>;
|
|
2034
|
+
}
|
|
2035
|
+
/** Whether `error` is a run's refused batch — what `records.batch` and every single write throw. */
|
|
2036
|
+
declare function isBatchRefused(error: unknown): error is FunctionBatchRefused;
|
|
2037
|
+
/** `records` in a run: exactly the Record types the reach names, and a batch over those it writes. */
|
|
2038
|
+
type FunctionRecords<R extends string, W extends string> = {
|
|
2039
|
+
readonly [K in R | W]: K extends W ? FunctionRecordWriter<K> : FunctionRecordReader<K>;
|
|
2040
|
+
} & {
|
|
2041
|
+
batch(collect: (batch: FunctionRecordBatch<W>) => undefined): Promise<FunctionBatchResult>;
|
|
2042
|
+
/**
|
|
2043
|
+
* A File for a File field of a Record type the Function writes (REL-3832): the bytes and the
|
|
2044
|
+
* name, and the media type when the name's extension does not say it. Answers at once — inside a
|
|
2045
|
+
* batch's collector too — and is set by the write that names it.
|
|
2046
|
+
*/
|
|
2047
|
+
file(bytes: Uint8Array | ArrayBuffer, options: {
|
|
2048
|
+
readonly name: string;
|
|
2049
|
+
readonly type?: string;
|
|
2050
|
+
}): FunctionFile;
|
|
2051
|
+
};
|
|
2052
|
+
/**
|
|
2053
|
+
* Which Way in started a run, or `editor`: a test run from the Function editor, which always runs in
|
|
2054
|
+
* the person's own Preview branch.
|
|
2055
|
+
*/
|
|
2056
|
+
type FunctionWayIn = 'sdk' | 'route' | 'tool' | 'schedule' | 'automation' | 'editor';
|
|
2057
|
+
/**
|
|
2058
|
+
* Whether a publishable key may call a Function from a browser (spec 4, REL-3892): `'required'` for
|
|
2059
|
+
* a signed-in End user only — the call carries their token — or `'optional'` with or without one.
|
|
2060
|
+
*/
|
|
2061
|
+
type FunctionEndUsers = 'required' | 'optional';
|
|
2062
|
+
/**
|
|
2063
|
+
* The Claims a run's End user carries: those the registered `relyio/extensions.ts` declares
|
|
2064
|
+
* (`ClaimsOf`), once `Register` names it — `declare module '@relyio/sdk' { interface Register {
|
|
2065
|
+
* extensions: typeof extensions } }` — else any name, each `unknown`.
|
|
2066
|
+
*/
|
|
2067
|
+
type FunctionEndUserClaims = Register extends {
|
|
2068
|
+
extensions: infer F;
|
|
2069
|
+
} ? ClaimsOf<F> : {
|
|
2070
|
+
readonly [claim: string]: unknown;
|
|
2071
|
+
};
|
|
2072
|
+
/**
|
|
2073
|
+
* The End user a run's call carried a verified token for (spec 4, REL-3892): their subject — the
|
|
2074
|
+
* `sub` an Owner field stores — and the Claims the Project declares, as the token carries them. A
|
|
2075
|
+
* Record the run creates naming no owner is theirs; what the run may touch is still its reach, never
|
|
2076
|
+
* their Policies.
|
|
2077
|
+
*/
|
|
2078
|
+
interface FunctionEndUser {
|
|
2079
|
+
readonly subject: string;
|
|
2080
|
+
readonly claims: FunctionEndUserClaims;
|
|
2081
|
+
}
|
|
2082
|
+
/** What a run knows of itself: its id survives every retry. */
|
|
2083
|
+
interface FunctionRunInfo {
|
|
2084
|
+
readonly id: string;
|
|
2085
|
+
readonly attempt: number;
|
|
2086
|
+
readonly wayIn: FunctionWayIn;
|
|
2087
|
+
/**
|
|
2088
|
+
* Who called, as the run record names them: `api-key:<keyId>`, `task:<taskId>` for a scheduled or
|
|
2089
|
+
* Automation run, or `user:<userId>` for a person's tool call or test run.
|
|
2090
|
+
*/
|
|
2091
|
+
readonly caller: string;
|
|
2092
|
+
readonly environment: 'production' | 'preview';
|
|
2093
|
+
/**
|
|
2094
|
+
* The Branch the run runs on (spec 3, REL-3853): what a checkout hands Stripe as
|
|
2095
|
+
* `metadata.relyio_branch`, so a Preview payment's events run that Branch's Automations.
|
|
2096
|
+
*/
|
|
2097
|
+
readonly branchId: string;
|
|
2098
|
+
/**
|
|
2099
|
+
* What started a run no request started: a schedule's firing — `{ type: 'function.schedule',
|
|
2100
|
+
* scheduledFor }`, the UTC instant it was for — or the event an Automation step reacts to.
|
|
2101
|
+
* Absent from a run an SDK call, a route or a tool started.
|
|
2102
|
+
*/
|
|
2103
|
+
readonly event?: Readonly<Record<string, unknown>>;
|
|
2104
|
+
/**
|
|
2105
|
+
* The End user whose token the call carried, verified (spec 4, REL-3892) — with any key on the SDK
|
|
2106
|
+
* Way in or a keyed route, and on a Public route that was sent one. Absent otherwise.
|
|
2107
|
+
*/
|
|
2108
|
+
readonly endUser?: FunctionEndUser;
|
|
2109
|
+
}
|
|
2110
|
+
/** Published content, read through the read core: present when the reach declares `content: 'published'`. */
|
|
2111
|
+
interface FunctionContent {
|
|
2112
|
+
get(type: string, id: string): Promise<Record<string, unknown> | null>;
|
|
2113
|
+
list(type: string, options?: {
|
|
2114
|
+
where?: Record<string, RecordConditionValue>;
|
|
2115
|
+
limit?: number;
|
|
2116
|
+
}): Promise<{
|
|
2117
|
+
data: Record<string, unknown>[];
|
|
2118
|
+
}>;
|
|
2119
|
+
}
|
|
2120
|
+
/**
|
|
2121
|
+
* A Function's input or output values, as its Fields declare them: what its run is handed and
|
|
2122
|
+
* returns, and what `relyio.functions.<name>(input)` takes and answers once `Register` names it.
|
|
2123
|
+
*/
|
|
2124
|
+
type FunctionFieldValues<F extends KeyedMembers> = FieldValues<DeclaredFields<{
|
|
2125
|
+
readonly fields: readonly KeyedMembersOf<F>[];
|
|
2126
|
+
}>>;
|
|
2127
|
+
/** What one Action handle's call takes and answers. */
|
|
2128
|
+
type ActionCallOf<H> = H extends AutomationAction<infer I, infer O, string, string> ? (input: I) => Promise<O> : never;
|
|
2129
|
+
/**
|
|
2130
|
+
* `actions` in a run (REL-3856): `actions.<extension>.<action>(input)` for exactly the Actions the
|
|
2131
|
+
* reach declares, typed by the Extension's own declaration. A call is sent through the events
|
|
2132
|
+
* worker's one send point with the run's Environment — the run holds no key — and answers the
|
|
2133
|
+
* Action's result; in a Preview without a Preview Connection, the Action's sample, the call Recorded
|
|
2134
|
+
* on the run. A refusal throws {@link FunctionActionRefused}.
|
|
2135
|
+
*/
|
|
2136
|
+
type FunctionActions<A extends FunctionActionHandle> = {
|
|
2137
|
+
readonly [E in A['extension']]: {
|
|
2138
|
+
readonly [N in Extract<A, {
|
|
2139
|
+
readonly extension: E;
|
|
2140
|
+
}>['action']]: ActionCallOf<Extract<A, {
|
|
2141
|
+
readonly extension: E;
|
|
2142
|
+
readonly action: N;
|
|
2143
|
+
}>>;
|
|
2144
|
+
};
|
|
2145
|
+
};
|
|
2146
|
+
/**
|
|
2147
|
+
* What a refused Action call throws in a run (REL-3856): nothing stands sent. `code` names why —
|
|
2148
|
+
* `extension-not-connected` (the Extension has no Connection in the run's Environment: Functions are
|
|
2149
|
+
* never paused, their call fails), `action-input-invalid`, `action-send-failed`, `action-refused`
|
|
2150
|
+
* (its third party refused it, with that party's message), `action-send-in-doubt` (it may have
|
|
2151
|
+
* arrived), or the API's own (`ACCESS_DENIED` for an Action outside the reach) — and `retryable`
|
|
2152
|
+
* whether another attempt could send it. A refused or in-doubt call is answered the same on the
|
|
2153
|
+
* run's next attempt, and never sent again.
|
|
2154
|
+
*/
|
|
2155
|
+
interface FunctionActionRefused extends Error {
|
|
2156
|
+
readonly name: 'ActionRefused';
|
|
2157
|
+
readonly code: string;
|
|
2158
|
+
readonly retryable: boolean;
|
|
2159
|
+
readonly extension: string;
|
|
2160
|
+
readonly action: string;
|
|
2161
|
+
}
|
|
2162
|
+
/** Whether `error` is a run's refused Action call. */
|
|
2163
|
+
declare function isActionRefused(error: unknown): error is FunctionActionRefused;
|
|
2164
|
+
/** Everything a run is handed (spec 2, "The capability object"). No key, no project id. */
|
|
2165
|
+
interface FunctionCapabilities<I extends KeyedMembers, R extends string, W extends string, C extends 'published' | 'none', S extends string, A extends FunctionActionHandle = never> {
|
|
2166
|
+
readonly input: FunctionFieldValues<I>;
|
|
2167
|
+
readonly records: FunctionRecords<R, W>;
|
|
2168
|
+
/** The Actions the reach declares, each sent without the run holding its key (REL-3856). */
|
|
2169
|
+
readonly actions: FunctionActions<A>;
|
|
2170
|
+
/** Published entries; `undefined` unless the reach declares `content: 'published'`. */
|
|
2171
|
+
readonly content: C extends 'published' ? FunctionContent : undefined;
|
|
2172
|
+
/** The gateway's fetch: only the hosts the reach declares answer. */
|
|
2173
|
+
readonly fetch: typeof globalThis.fetch;
|
|
2174
|
+
/** Each declared Secret as a placeholder the gateway swaps for its value on the way to its hosts. */
|
|
2175
|
+
readonly secrets: {
|
|
2176
|
+
readonly [K in S]: string;
|
|
2177
|
+
};
|
|
2178
|
+
readonly run: FunctionRunInfo;
|
|
2179
|
+
/**
|
|
2180
|
+
* The HTTP request, for a run a route started. Its body is the bytes the caller sent, unparsed —
|
|
2181
|
+
* `await request.text()` or `arrayBuffer()` is what a webhook's sender signed.
|
|
2182
|
+
*/
|
|
2183
|
+
readonly request: Request | undefined;
|
|
2184
|
+
}
|
|
2185
|
+
/**
|
|
2186
|
+
* A response a route Function shapes itself (REL-3829): return `respond({ … })` from `run` and the
|
|
2187
|
+
* route answers with that status, those headers and that body — a redirect after a form post, a
|
|
2188
|
+
* `201`, a webhook's plain-text `ok`. A string body is sent as it is; any other value as JSON;
|
|
2189
|
+
* `null` or none sends no body. Any other output a route Function returns is answered as JSON with
|
|
2190
|
+
* `200`. The hop's own headers (`content-length`, `transfer-encoding`, …) are RelyIO's to set.
|
|
2191
|
+
*/
|
|
2192
|
+
interface FunctionResponse {
|
|
2193
|
+
readonly _tag: 'function-response';
|
|
2194
|
+
readonly status: number;
|
|
2195
|
+
readonly headers: Readonly<Record<string, string>>;
|
|
2196
|
+
readonly body: unknown;
|
|
2197
|
+
}
|
|
2198
|
+
/**
|
|
2199
|
+
* Shape a route Function's response.
|
|
2200
|
+
*
|
|
2201
|
+
* @example
|
|
2202
|
+
* return respond({ status: 303, headers: { location: 'https://acme.example/thanks' } })
|
|
2203
|
+
*/
|
|
2204
|
+
declare function respond(response: {
|
|
2205
|
+
/** 200–599. */
|
|
2206
|
+
readonly status: number;
|
|
2207
|
+
readonly headers?: Readonly<Record<string, string>>;
|
|
2208
|
+
readonly body?: unknown;
|
|
2209
|
+
}): FunctionResponse;
|
|
2210
|
+
/** What `defineFunction` takes. Every part but `run` is data. */
|
|
2211
|
+
interface FunctionConfig<I extends KeyedMembers, O extends KeyedMembers, R extends FunctionRecordTypeName, W extends FunctionRecordTypeName, C extends 'published' | 'none', S extends string, A extends FunctionActionHandle = never> {
|
|
2212
|
+
description?: string;
|
|
2213
|
+
/** `'isolate'` when left out. */
|
|
2214
|
+
runtime?: FunctionRuntime;
|
|
2215
|
+
container?: {
|
|
2216
|
+
size: FunctionContainerSize;
|
|
2217
|
+
};
|
|
2218
|
+
limits?: {
|
|
2219
|
+
cpu?: FunctionDuration;
|
|
2220
|
+
duration?: FunctionDuration;
|
|
2221
|
+
};
|
|
2222
|
+
/** The input's Fields, under their ids, as the content model's `fields.*` write them. */
|
|
2223
|
+
input?: I;
|
|
2224
|
+
output?: O;
|
|
2225
|
+
/** Reach: the Record types it reads and writes, by name. None when left out. */
|
|
2226
|
+
records?: {
|
|
2227
|
+
read?: readonly R[];
|
|
2228
|
+
write?: readonly W[];
|
|
2229
|
+
};
|
|
2230
|
+
/** Reach: `'published'` to read published content. `'none'` when left out. */
|
|
2231
|
+
content?: C;
|
|
2232
|
+
/** Reach: the hosts it calls. None when left out. */
|
|
2233
|
+
network?: readonly FunctionNetworkHost[];
|
|
2234
|
+
secrets?: {
|
|
2235
|
+
readonly [K in S]: FunctionSecretDeclaration;
|
|
2236
|
+
};
|
|
2237
|
+
/**
|
|
2238
|
+
* Reach: the Extensions' Actions it may send, by the handles `relyio/extensions.ts` gives —
|
|
2239
|
+
* `actions: [resend.send]`. None when left out; `actions.<extension>.<action>` for any other is a
|
|
2240
|
+
* type error, and refused at run time.
|
|
2241
|
+
*/
|
|
2242
|
+
actions?: readonly A[];
|
|
2243
|
+
/**
|
|
2244
|
+
* Who besides a secret key may call it through `relyio.functions.<name>(input)` (spec 4,
|
|
2245
|
+
* REL-3892): `'required'` lets a publishable key call it for a signed-in End user, whose token it
|
|
2246
|
+
* carries; `'optional'` lets a publishable key call it with or without one. Left out, a secret key
|
|
2247
|
+
* alone. Either way the run is bound by its reach, and `run.endUser` names who called.
|
|
2248
|
+
*/
|
|
2249
|
+
endUsers?: FunctionEndUsers;
|
|
2250
|
+
route?: FunctionRoute;
|
|
2251
|
+
tool?: FunctionTool;
|
|
2252
|
+
schedule?: FunctionSchedule;
|
|
2253
|
+
run(capabilities: FunctionCapabilities<I, R, W, C, S, A>): Promise<FunctionFieldValues<O> | FunctionResponse | void> | FunctionFieldValues<O> | FunctionResponse | void;
|
|
2254
|
+
}
|
|
2255
|
+
/** Where a Function's input and output types ride on its definition, for the compiler alone. */
|
|
2256
|
+
declare const FUNCTION_FIELDS: unique symbol;
|
|
2257
|
+
/**
|
|
2258
|
+
* What a Function file default-exports: its declaration as data, and its code. `I` and `O` are its
|
|
2259
|
+
* input and output Fields, carried for the compiler alone, so an Automation's Function step that
|
|
2260
|
+
* takes it by import types its input and its output from them (spec 3, REL-3845).
|
|
2261
|
+
*/
|
|
2262
|
+
interface FunctionDefinition<I extends KeyedMembers = KeyedMembers, O extends KeyedMembers = KeyedMembers> {
|
|
2263
|
+
readonly _tag: 'function';
|
|
2264
|
+
readonly declaration: FunctionDeclaration;
|
|
2265
|
+
readonly run: (capabilities: never) => unknown;
|
|
2266
|
+
/** Never present at run time: the input and output Fields, as types. */
|
|
2267
|
+
readonly [FUNCTION_FIELDS]?: {
|
|
2268
|
+
readonly input: I;
|
|
2269
|
+
readonly output: O;
|
|
2270
|
+
};
|
|
2271
|
+
}
|
|
2272
|
+
/** A Function's input values, as its Fields declare them. */
|
|
2273
|
+
type FunctionInputOf<F> = F extends FunctionDefinition<infer I, KeyedMembers> ? FunctionFieldValues<I> : never;
|
|
2274
|
+
/** A Function's output values, as its Fields declare them. */
|
|
2275
|
+
type FunctionOutputOf<F> = F extends FunctionDefinition<KeyedMembers, infer O> ? FunctionFieldValues<O> : never;
|
|
2276
|
+
/**
|
|
2277
|
+
* Declare a Function. The file's name is its name; `relyio deploy` reads the declaration, bundles the
|
|
2278
|
+
* file and ships both with the schema.
|
|
2279
|
+
*/
|
|
2280
|
+
declare function defineFunction<const I extends KeyedMembers = Record<never, never>, const O extends KeyedMembers = Record<never, never>, const R extends FunctionRecordTypeName = never, const W extends FunctionRecordTypeName = never, const C extends 'published' | 'none' = 'none', const S extends string = never, const A extends FunctionActionHandle = never>(config: FunctionConfig<I, O, R, W, C, S, A>): FunctionDefinition<I, O>;
|
|
2281
|
+
/** Whether `value` is what `defineFunction` returns: how the CLI reads a Function file's default export. */
|
|
2282
|
+
declare function isFunctionDefinition(value: unknown): value is FunctionDefinition;
|
|
2283
|
+
|
|
2284
|
+
export { type AutomationTemplate as $, type AnySchemaDefinition as A, type BaseRecord as B, type RecordConditionValue as C, DefaultRetryPolicy as D, type AutomationRecordAction as E, type FunctionFieldValues as F, type AutomationEntryAction as G, HttpClient as H, type AutomationBecomesTest as I, type AutomationWhere as J, type AutomationTrigger as K, type AutomationJson as L, type MatchRouteOptions as M, type AutomationRef as N, type AutomationValue as O, type AutomationInput as P, type QueryOf as Q, type Register as R, type SendOptions as S, type AutomationDuration as T, type AutomationItemRef as U, type AutomationLookupState as V, type Wire as W, type AutomationProposeTarget as X, type AutomationStep as Y, type AutomationDefinition as Z, type Ref as _, type RegisteredSchema as a, isExtensionsFile as a$, type Accepts as a0, type StepInput as a1, tpl as a2, and as a3, or as a4, not as a5, type AutomationTriggerHandle as a6, type RecordEvent as a7, type EntryEvent as a8, type TriggerWhere as a9, type LookedUpEntry as aA, type LookedUp as aB, type LookupOptions as aC, type AskStepConfig as aD, type AskOutput as aE, type ApproveStepConfig as aF, type ApproveCounts as aG, type ApproveOutput as aH, type StepBuilder as aI, type AutomationConfig as aJ, type AutomationFile as aK, defineAutomation as aL, isAutomationFile as aM, resolveAutomation as aN, type ExtensionUse as aO, type ExtensionsDeclaration as aP, type ExtensionSettings as aQ, type ExtensionsConfig as aR, type ExtensionTriggerHandle as aS, type HandleOf as aT, type ContributedFieldOptions as aU, type ContributedFieldKindBuilder as aV, type ExtensionHandle as aW, type ExtensionsFile as aX, type ClaimsOf as aY, defineExtensions as aZ, type ExtensionsFileData as a_, type UidRef as aa, type AutomationItemHandle as ab, type RecordTriggers as ac, type EntryTriggers as ad, records as ae, entries as af, type AnyEntryEvent as ag, type AnyEntryTriggers as ah, anyEntry as ai, schedule as aj, type RunFailedEvent as ak, type RunFailedWhere as al, automations as am, type ExtensionTriggerEvent as an, extensionTrigger as ao, on as ap, type AutomationAction as aq, type AutomationStepDraft as ar, type StepHandle as as, type StepWithOutput as at, type AgentStepConfig as au, type ProposingAgentStepConfig as av, type AutomationProposal as aw, type ProposingOutput as ax, type LookedUpRecord as ay, type LookedUpEntryItem as az, type ApiPath as b, extensionsDeclarationOf as b0, type FunctionRuntime as b1, type FunctionContainerSize as b2, type FunctionDuration as b3, type FunctionRouteMethod as b4, type FunctionNetworkHost as b5, type FunctionActionReach as b6, type FunctionActionHandle as b7, type FunctionSecretDeclaration as b8, type FunctionRoute as b9, type FunctionConfig as bA, type FunctionDefinition as bB, type FunctionInputOf as bC, type FunctionOutputOf as bD, defineFunction as bE, isFunctionDefinition as bF, type FunctionTool as ba, type FunctionSchedule as bb, type FunctionFieldDeclaration as bc, type FunctionDeclaration as bd, type FunctionRecordTypeName as be, type FunctionFile as bf, type FunctionCounterMove as bg, type FunctionRecordReader as bh, type FunctionRecordWriter as bi, type FunctionRecordBatch as bj, type FunctionBatchResult as bk, type FunctionBatchRefused as bl, isBatchRefused as bm, type FunctionRecords as bn, type FunctionWayIn as bo, type FunctionEndUsers as bp, type FunctionEndUserClaims as bq, type FunctionEndUser as br, type FunctionRunInfo as bs, type FunctionContent as bt, type FunctionActions as bu, type FunctionActionRefused as bv, isActionRefused as bw, type FunctionCapabilities as bx, type FunctionResponse as by, respond as bz, type ApiMethod as c, type ApiSuccessBody as d, type RouteMatch as e, type RetryPolicy as f, type RetryableRequest as g, RecordsClient as h, isReplaySafe as i, type RecordCallOptions as j, type RecordFacetBucket as k, type RecordFileBytes as l, matchRoute as m, type RecordFileUploadOptions as n, type RecordList as o, type RecordListOptions as p, type RecordMetadata as q, retryPolicyFor as r, type RecordReadOptions as s, type RecordUpdateOptions as t, refusedOperationOf as u, type RecordBatchOperation as v, type RecordBatchOperationResult as w, type RecordBatchOptions as x, type RecordBatchResult as y, type RecordBatchTarget as z };
|