@rebasepro/types 0.17.3 → 0.18.1
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/README.md +4 -0
- package/dist/call_context.d.ts +20 -0
- package/dist/controllers/client.d.ts +36 -4
- package/dist/controllers/data.d.ts +120 -10
- package/dist/errors.d.ts +83 -4
- package/dist/index.es.js +522 -160
- package/dist/index.es.js.map +1 -1
- package/dist/types/admin_block.d.ts +2 -2
- package/dist/types/auth_adapter.d.ts +41 -6
- package/dist/types/backend.d.ts +48 -0
- package/dist/types/collections.d.ts +25 -1
- package/dist/types/cron.d.ts +34 -0
- package/dist/types/database_adapter.d.ts +39 -0
- package/dist/types/entity_callbacks.d.ts +14 -1
- package/dist/types/filter-operators.d.ts +24 -1
- package/dist/types/policy.d.ts +29 -1
- package/dist/types/properties.d.ts +216 -3
- package/dist/types/relations.d.ts +65 -7
- package/dist/types/resource_kinds.d.ts +173 -17
- package/dist/types/resources.d.ts +108 -7
- package/dist/types/rls-functions.d.ts +11 -0
- package/dist/types/storage_source.d.ts +12 -23
- package/package.json +24 -23
- package/src/call_context.ts +0 -120
- package/src/controllers/auth_state.ts +0 -24
- package/src/controllers/client.ts +0 -494
- package/src/controllers/collection_registry.ts +0 -62
- package/src/controllers/data.ts +0 -1012
- package/src/controllers/data_driver.ts +0 -576
- package/src/controllers/effective_role.ts +0 -4
- package/src/controllers/email.ts +0 -91
- package/src/controllers/index.ts +0 -11
- package/src/controllers/storage.ts +0 -252
- package/src/errors.ts +0 -119
- package/src/index.ts +0 -5
- package/src/types/admin_block.ts +0 -209
- package/src/types/api_keys.ts +0 -108
- package/src/types/auth_adapter.ts +0 -580
- package/src/types/backend.ts +0 -987
- package/src/types/backup.ts +0 -26
- package/src/types/channel_bus.ts +0 -202
- package/src/types/chips.ts +0 -34
- package/src/types/collection_contract.ts +0 -278
- package/src/types/collections.ts +0 -763
- package/src/types/component_ref.ts +0 -92
- package/src/types/cron.ts +0 -213
- package/src/types/data_source.ts +0 -357
- package/src/types/database_adapter.ts +0 -267
- package/src/types/entities.ts +0 -226
- package/src/types/entity_callbacks.ts +0 -229
- package/src/types/filter-operators.ts +0 -444
- package/src/types/history.ts +0 -66
- package/src/types/index.ts +0 -36
- package/src/types/indexes.ts +0 -180
- package/src/types/policy.ts +0 -328
- package/src/types/postgres_introspection.ts +0 -101
- package/src/types/project_manifest.ts +0 -598
- package/src/types/properties.ts +0 -1368
- package/src/types/relations.ts +0 -417
- package/src/types/resource_kinds.ts +0 -390
- package/src/types/resources.ts +0 -368
- package/src/types/rls-functions.ts +0 -98
- package/src/types/schema_editing.ts +0 -157
- package/src/types/schema_version.ts +0 -112
- package/src/types/search.ts +0 -247
- package/src/types/security_rules.ts +0 -344
- package/src/types/storage_authorize.ts +0 -77
- package/src/types/storage_source.ts +0 -248
- package/src/types/websockets.ts +0 -117
- package/src/users/index.ts +0 -2
- package/src/users/user.ts +0 -69
package/src/controllers/data.ts
DELETED
|
@@ -1,1012 +0,0 @@
|
|
|
1
|
-
import type { VectorSearchParams } from "./data_driver";
|
|
2
|
-
import type { ComputedSortField, SearchMatch } from "../types/search";
|
|
3
|
-
import { Entity, EntityValues } from "../types/entities";
|
|
4
|
-
import { WhereFilterOp, FieldPath, FilterValues, OrderBySpec } from "../types/filter-operators";
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* The element type of an array column, and the column's own type otherwise.
|
|
8
|
-
*
|
|
9
|
-
* A generated SDK emits an `array` property as `Array<X>` and a to-many
|
|
10
|
-
* relation as `Array<TargetRow>`, so this is what `array-contains` compares
|
|
11
|
-
* against on either.
|
|
12
|
-
*/
|
|
13
|
-
export type ElementOf<T> = T extends readonly (infer E)[] ? E : T;
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* The `id` of a row-shaped element, and `never` for anything else.
|
|
17
|
-
*
|
|
18
|
-
* A to-many relation is emitted as `Array<TargetRow>`, but the filter compilers
|
|
19
|
-
* compare a relation by **id** — `buildRelationFilterPredicate` in
|
|
20
|
-
* `@rebasepro/server-postgres` unwraps a relation value down to its id — so
|
|
21
|
-
* `where("tags", "array-contains", tagId)` is the call that works, and the
|
|
22
|
-
* element type alone would refuse it.
|
|
23
|
-
*/
|
|
24
|
-
export type IdOf<E> = E extends { id: infer I } ? I : never;
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* One member of an array column: its element, or — when the element is a row —
|
|
28
|
-
* that row's id, which is what a relation filter is actually compared against.
|
|
29
|
-
*/
|
|
30
|
-
export type WhereElementOf<T> = ElementOf<T> | IdOf<ElementOf<T>>;
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* The value a given operator takes on a column of type `T`.
|
|
34
|
-
*
|
|
35
|
-
* `WhereValue<T>` was one value type for all sixteen operators, which made
|
|
36
|
-
* `array-contains` uncallable from a generated SDK — it is the one operator
|
|
37
|
-
* whose value is an *element* of the column rather than the column's own type,
|
|
38
|
-
* so on `tags: string[]` it wanted a `string[]` and the documented
|
|
39
|
-
* `.where("tags", "array-contains", "featured")` was a compile error. The
|
|
40
|
-
* spelling that did compile, `["featured"]`, builds `@> ARRAY[$1]` with the
|
|
41
|
-
* whole array bound as the single element and matches nothing: the correct
|
|
42
|
-
* query rejected, the accepted query silently wrong.
|
|
43
|
-
*
|
|
44
|
-
* The branches mirror `buildSingleFilterCondition` in `@rebasepro/server-postgres`:
|
|
45
|
-
*
|
|
46
|
-
* - `array-contains` → one element of the column (or a related row's id).
|
|
47
|
-
* - `in` / `not-in` / `array-contains-any` → a list of elements; a bare element
|
|
48
|
-
* is read as the one-element list, and `null` is a null check.
|
|
49
|
-
* - `like` / `ilike` / `not-like` / `not-ilike` → a SQL pattern. Always a
|
|
50
|
-
* string, including on numeric and date columns, which the driver casts.
|
|
51
|
-
* - `is-null` / `is-not-null` → nothing; the value is ignored everywhere.
|
|
52
|
-
* - everything else → the column's own type, or `null` for a null comparison.
|
|
53
|
-
*
|
|
54
|
-
* Distributes over `Op`, so a caller holding an unnarrowed `WhereFilterOp`
|
|
55
|
-
* (a dynamic filter UI, say) gets the union of every branch and stays as
|
|
56
|
-
* permissive as it was.
|
|
57
|
-
*/
|
|
58
|
-
export type WhereValueFor<Op extends WhereFilterOp, T> =
|
|
59
|
-
Op extends "array-contains"
|
|
60
|
-
? WhereElementOf<T>
|
|
61
|
-
: Op extends "in" | "not-in" | "array-contains-any"
|
|
62
|
-
? readonly WhereElementOf<T>[] | WhereElementOf<T> | null
|
|
63
|
-
: Op extends "like" | "ilike" | "not-like" | "not-ilike"
|
|
64
|
-
? string
|
|
65
|
-
: Op extends "is-null" | "is-not-null"
|
|
66
|
-
? null | undefined
|
|
67
|
-
: T | null;
|
|
68
|
-
|
|
69
|
-
export interface LogicalCondition {
|
|
70
|
-
type: "and" | "or";
|
|
71
|
-
conditions: (FilterCondition | LogicalCondition)[];
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
export interface FilterCondition {
|
|
75
|
-
column: string;
|
|
76
|
-
operator: WhereFilterOp;
|
|
77
|
-
value: unknown;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Parameters for querying a collection.
|
|
82
|
-
*
|
|
83
|
-
* ## How the filter parameters combine
|
|
84
|
-
*
|
|
85
|
-
* `where`, `logical`, and `searchString` are **independent** and, when more
|
|
86
|
-
* than one is present, are combined with **AND** — every clause must match.
|
|
87
|
-
* Concretely the backend builds:
|
|
88
|
-
*
|
|
89
|
-
* ```text
|
|
90
|
-
* (where filters, AND-ed together)
|
|
91
|
-
* AND (logical group)
|
|
92
|
-
* AND (searchString matches, OR-ed across searchable columns)
|
|
93
|
-
* ```
|
|
94
|
-
*
|
|
95
|
-
* So `where` does **not** conflict with or override `logical` — they stack.
|
|
96
|
-
* If you need `where` fields OR-ed with each other, move them into `logical`
|
|
97
|
-
* instead. There is no way to OR `where` against `logical`; express anything
|
|
98
|
-
* that isn't a plain AND of the three groups inside a single `logical` tree.
|
|
99
|
-
*
|
|
100
|
-
* ## Pagination precedence
|
|
101
|
-
*
|
|
102
|
-
* `limit`/`offset` and `page` describe the same window two ways. If **both
|
|
103
|
-
* `offset` and `page` are provided, `page` wins** — the backend computes
|
|
104
|
-
* `offset = (page - 1) * (limit ?? DEFAULT_LIST_LIMIT)` and ignores the
|
|
105
|
-
* explicit `offset`. Pick one style per query.
|
|
106
|
-
*
|
|
107
|
-
* @group Data
|
|
108
|
-
*/
|
|
109
|
-
export interface FindParams<M extends Record<string, unknown> = Record<string, unknown>> {
|
|
110
|
-
/**
|
|
111
|
-
* Maximum number of items to return.
|
|
112
|
-
*
|
|
113
|
-
* Omit it and the backend applies {@link DEFAULT_LIST_LIMIT}, so a read is
|
|
114
|
-
* never unbounded. Provide it and it must be a whole number between 1 and
|
|
115
|
-
* {@link MAX_LIST_LIMIT}: the backend **rejects** anything else with a 400
|
|
116
|
-
* rather than trimming it to fit, because a page quietly smaller than the
|
|
117
|
-
* one you asked for is indistinguishable from having reached the end of the
|
|
118
|
-
* collection. To read past the ceiling, page with `offset` — or let
|
|
119
|
-
* {@link SDKCollectionClient.iterate} / {@link SDKCollectionClient.findAll}
|
|
120
|
-
* do it for you.
|
|
121
|
-
*/
|
|
122
|
-
limit?: number;
|
|
123
|
-
/**
|
|
124
|
-
* Number of items to skip. Ignored when {@link FindParams.page} is also
|
|
125
|
-
* set — `page` takes precedence.
|
|
126
|
-
*/
|
|
127
|
-
offset?: number;
|
|
128
|
-
/**
|
|
129
|
-
* Page number (1-indexed), alternative to {@link FindParams.offset}.
|
|
130
|
-
* When set, overrides `offset` as `(page - 1) * (limit ?? DEFAULT_LIST_LIMIT)`.
|
|
131
|
-
*/
|
|
132
|
-
page?: number;
|
|
133
|
-
/**
|
|
134
|
-
* Filter conditions keyed by field name.
|
|
135
|
-
* Each value is a `[WhereFilterOp, value]` tuple or an array of tuples
|
|
136
|
-
* for multiple conditions on the same field. Multiple fields, and multiple
|
|
137
|
-
* tuples on one field, are **AND-ed**; also AND-ed with `logical` and
|
|
138
|
-
* `searchString` when present (see the interface docs).
|
|
139
|
-
*
|
|
140
|
-
* @example
|
|
141
|
-
* { status: ["==", "active"] }
|
|
142
|
-
* { age: [">=", 18] }
|
|
143
|
-
* { role: ["in", ["admin", "editor"]] }
|
|
144
|
-
* { age: [[">=", 18], ["<", 65]] }
|
|
145
|
-
*/
|
|
146
|
-
where?: FilterValues<FieldPath<M>>;
|
|
147
|
-
/**
|
|
148
|
-
* Logical grouping conditions (AND/OR). Use this for anything `where`
|
|
149
|
-
* can't express — notably OR-ing conditions. AND-ed with `where` and
|
|
150
|
-
* `searchString` when present (see the interface docs).
|
|
151
|
-
*/
|
|
152
|
-
logical?: LogicalCondition;
|
|
153
|
-
/**
|
|
154
|
-
* Sort order as a `[field, direction]` tuple, or a list of them applied in
|
|
155
|
-
* order of significance — the second key breaks ties on the first, and so on.
|
|
156
|
-
*
|
|
157
|
-
* @example orderBy: ["created_at", "desc"]
|
|
158
|
-
* @example orderBy: [["roles", "asc"], ["created_at", "desc"]]
|
|
159
|
-
*/
|
|
160
|
-
orderBy?: OrderBySpec<FieldPath<M> | ComputedSortField>;
|
|
161
|
-
/**
|
|
162
|
-
* Relations to include in the response.
|
|
163
|
-
*
|
|
164
|
-
* Deliberately `string[]` and not checked against `M`: a relation name
|
|
165
|
-
* comes from the collection's `relations`, not from its columns, so nothing
|
|
166
|
-
* in a generated row type can validate one.
|
|
167
|
-
*/
|
|
168
|
-
include?: string[];
|
|
169
|
-
/**
|
|
170
|
-
* Text search string, AND-ed with `where`/`logical`. This is the value
|
|
171
|
-
* behind the query builder's `.search()` method.
|
|
172
|
-
*
|
|
173
|
-
* What it compiles to depends on the collection. By default — matching
|
|
174
|
-
* every collection that has not said otherwise — it is a case-insensitive
|
|
175
|
-
* substring match OR-ed across the collection's top-level `string`
|
|
176
|
-
* properties: it does not reach inside `map` or `array` properties, it does
|
|
177
|
-
* not stem or rank, and it cannot use an index.
|
|
178
|
-
*
|
|
179
|
-
* A Postgres collection that declares a `search` block instead gets a
|
|
180
|
-
* ranked full-text match over exactly the fields it named, and rows come
|
|
181
|
-
* back with a {@link FindParams.orderBy}-able `_score`.
|
|
182
|
-
*/
|
|
183
|
-
searchString?: string;
|
|
184
|
-
|
|
185
|
-
/**
|
|
186
|
-
* Nearest-neighbour search over a `vector` property.
|
|
187
|
-
*
|
|
188
|
-
* Postgres only, and only for a collection that declares a property of
|
|
189
|
-
* type `vector`. Rows come back ordered by distance, closest first, each
|
|
190
|
-
* carrying a `_distance`. Combines with `where` and `logical`, which are
|
|
191
|
-
* applied as filters before the ordering — so this is "the nearest rows
|
|
192
|
-
* that also match", not "the nearest rows, then filtered".
|
|
193
|
-
*
|
|
194
|
-
* Supplying the query vector is the caller's job: rebase stores and
|
|
195
|
-
* searches embeddings, it does not compute them.
|
|
196
|
-
*/
|
|
197
|
-
vectorSearch?: VectorSearchParams;
|
|
198
|
-
|
|
199
|
-
/**
|
|
200
|
-
* Ask each returned row to explain itself: which declared search fields
|
|
201
|
-
* matched, with a highlighted snippet from each. Populates `_matches`.
|
|
202
|
-
*
|
|
203
|
-
* Off by default because it is not free — one `ts_headline` per declared
|
|
204
|
-
* field per returned row, and `ts_headline` re-parses the document rather
|
|
205
|
-
* than reading the index. Fine for a page of results, not for an export.
|
|
206
|
-
*
|
|
207
|
-
* Ignored unless the collection declares a `search` block and the query
|
|
208
|
-
* carries a `searchString`; there is nothing to explain otherwise.
|
|
209
|
-
*/
|
|
210
|
-
searchExplain?: boolean;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
/**
|
|
214
|
-
* Paginated response from a collection query.
|
|
215
|
-
* @group Data
|
|
216
|
-
*/
|
|
217
|
-
export interface FindResponse<M extends Record<string, unknown> = Record<string, unknown>> {
|
|
218
|
-
/** Array of entities matching the query */
|
|
219
|
-
data: Entity<M>[];
|
|
220
|
-
/** Pagination metadata */
|
|
221
|
-
meta: {
|
|
222
|
-
total: number;
|
|
223
|
-
limit: number;
|
|
224
|
-
offset: number;
|
|
225
|
-
hasMore: boolean;
|
|
226
|
-
};
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
/**
|
|
232
|
-
* Fluent query builder for the **admin panel** — resolves to `FindResponse<M>`
|
|
233
|
-
* (Snapshot-wrapped rows).
|
|
234
|
-
*
|
|
235
|
-
* @internal App developers should use {@link SDKQueryBuilderInterface}
|
|
236
|
-
* (flat rows, returned by `client.data.*` / `context.data.*`). This
|
|
237
|
-
* Snapshot-flavored variant backs the admin panel internals only.
|
|
238
|
-
*
|
|
239
|
-
* @group Data
|
|
240
|
-
*/
|
|
241
|
-
export interface QueryBuilderInterface<M extends Record<string, unknown> = Record<string, unknown>> {
|
|
242
|
-
where<K extends keyof M & string, Op extends WhereFilterOp>(column: K, operator: Op, value: WhereValueFor<Op, M[K]>): this;
|
|
243
|
-
where(logicalCondition: LogicalCondition): this;
|
|
244
|
-
orderBy(column: (keyof M & string) | ComputedSortField, direction?: "asc" | "desc"): this;
|
|
245
|
-
limit(count: number): this;
|
|
246
|
-
offset(count: number): this;
|
|
247
|
-
search(searchString: string, options?: { explain?: boolean }): this;
|
|
248
|
-
|
|
249
|
-
/**
|
|
250
|
-
* Order rows by nearest-neighbour distance to `vector`, closest first.
|
|
251
|
-
*
|
|
252
|
-
* Postgres only, over a property declared as `type: "vector"`. Each row
|
|
253
|
-
* comes back with a `_distance`. Any `where` on the same query filters
|
|
254
|
-
* before the ordering; distance decides the order.
|
|
255
|
-
*
|
|
256
|
-
* The query embedding is the caller's to produce.
|
|
257
|
-
*/
|
|
258
|
-
vectorSearch(
|
|
259
|
-
property: string,
|
|
260
|
-
vector: number[],
|
|
261
|
-
options?: { distance?: "cosine" | "l2" | "inner_product"; threshold?: number }
|
|
262
|
-
): this;
|
|
263
|
-
include(...relations: string[]): this;
|
|
264
|
-
find(): Promise<FindResponse<M>>;
|
|
265
|
-
listen(onUpdate: (data: FindResponse<M>) => void, onError?: (error: Error) => void): () => void;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
/**
|
|
269
|
-
* A single collection's CRUD accessor for the **admin panel** — every method
|
|
270
|
-
* resolves to `Snapshot`-wrapped rows (`FindResponse<M>` / `Snapshot<M>`).
|
|
271
|
-
*
|
|
272
|
-
* @internal App developers do **not** use this. The public, symmetric surface
|
|
273
|
-
* is {@link SDKCollectionClient} (flat rows), exposed as `client.data.products`
|
|
274
|
-
* in the SDK and `context.data.products` in framework callbacks. This
|
|
275
|
-
* Snapshot-flavored accessor backs the admin panel view-model only.
|
|
276
|
-
*
|
|
277
|
-
* @group Data
|
|
278
|
-
*/
|
|
279
|
-
export interface CollectionAccessor<M extends Record<string, unknown> = Record<string, unknown>> {
|
|
280
|
-
/**
|
|
281
|
-
* Find multiple records with optional filtering, pagination, and sorting.
|
|
282
|
-
*/
|
|
283
|
-
find(params?: FindParams<M>): Promise<FindResponse<M>>;
|
|
284
|
-
|
|
285
|
-
/**
|
|
286
|
-
* Find a single record by its ID.
|
|
287
|
-
*/
|
|
288
|
-
findById(id: string | number): Promise<Entity<M> | undefined>;
|
|
289
|
-
|
|
290
|
-
/**
|
|
291
|
-
* Create a new record.
|
|
292
|
-
* @param data The entity data to create.
|
|
293
|
-
* @param id Optional specific ID to use for the new record.
|
|
294
|
-
* @returns The created entity
|
|
295
|
-
*/
|
|
296
|
-
create(data: Partial<EntityValues<M>>, id?: string | number): Promise<Entity<M>>;
|
|
297
|
-
|
|
298
|
-
/**
|
|
299
|
-
* Create many records in a single transaction.
|
|
300
|
-
*
|
|
301
|
-
* See {@link SDKCollectionClient.createMany}. Optional: not every driver can
|
|
302
|
-
* write in bulk, and callers should fall back to `create` per record.
|
|
303
|
-
*/
|
|
304
|
-
createMany?(data: Partial<EntityValues<M>>[], options?: { upsert?: boolean }): Promise<Entity<M>[]>;
|
|
305
|
-
|
|
306
|
-
/**
|
|
307
|
-
* Update an existing record by ID.
|
|
308
|
-
* @returns The updated entity
|
|
309
|
-
*/
|
|
310
|
-
update(id: string | number, data: Partial<EntityValues<M>>): Promise<Entity<M>>;
|
|
311
|
-
|
|
312
|
-
/**
|
|
313
|
-
* Update many records in a single transaction.
|
|
314
|
-
*
|
|
315
|
-
* See {@link SDKCollectionClient.updateMany}. Optional, as `createMany` is.
|
|
316
|
-
*/
|
|
317
|
-
updateMany?(updates: { id: string | number; data: Partial<EntityValues<M>> }[]): Promise<Entity<M>[]>;
|
|
318
|
-
|
|
319
|
-
/**
|
|
320
|
-
* Delete many records in a single transaction.
|
|
321
|
-
*
|
|
322
|
-
* See {@link SDKCollectionClient.deleteMany}. Optional, as `createMany` is.
|
|
323
|
-
*/
|
|
324
|
-
deleteMany?(ids: (string | number)[]): Promise<void>;
|
|
325
|
-
|
|
326
|
-
/**
|
|
327
|
-
* Delete a record by ID.
|
|
328
|
-
*/
|
|
329
|
-
delete(id: string | number): Promise<void>;
|
|
330
|
-
|
|
331
|
-
/**
|
|
332
|
-
* Subscribe to a collection for real-time updates.
|
|
333
|
-
* Optional method, may not be supported by all implementations (like stateless HTTP clients).
|
|
334
|
-
*/
|
|
335
|
-
listen?(params: FindParams<M> | undefined, onUpdate: (response: FindResponse<M>) => void, onError?: (error: Error) => void): () => void;
|
|
336
|
-
|
|
337
|
-
/**
|
|
338
|
-
* Subscribe to a single record for real-time updates.
|
|
339
|
-
* Optional method.
|
|
340
|
-
*/
|
|
341
|
-
listenById?(id: string | number, onUpdate: (entity: Entity<M> | undefined) => void, onError?: (error: Error) => void): () => void;
|
|
342
|
-
|
|
343
|
-
/**
|
|
344
|
-
* Count the number of records matching the given filter.
|
|
345
|
-
*
|
|
346
|
-
* Optional on this contract because a data source need not support it, and
|
|
347
|
-
* required on `CollectionClient` — the HTTP implementation always has it.
|
|
348
|
-
* So `client.data.posts.count()` compiles in the browser while the same
|
|
349
|
-
* call through a `context.data` accessor needs `count?.()`, which is the
|
|
350
|
-
* one place the two halves of this API are not interchangeable.
|
|
351
|
-
*/
|
|
352
|
-
count?(params?: FindParams<M>): Promise<number>;
|
|
353
|
-
|
|
354
|
-
// Fluent Query Builder
|
|
355
|
-
where<K extends keyof M & string, Op extends WhereFilterOp>(column: K, operator: Op, value: WhereValueFor<Op, M[K]>): QueryBuilderInterface<M>;
|
|
356
|
-
where(logicalCondition: LogicalCondition): QueryBuilderInterface<M>;
|
|
357
|
-
orderBy(column: (keyof M & string) | ComputedSortField, direction?: "asc" | "desc"): QueryBuilderInterface<M>;
|
|
358
|
-
limit(count: number): QueryBuilderInterface<M>;
|
|
359
|
-
offset(count: number): QueryBuilderInterface<M>;
|
|
360
|
-
search(searchString: string, options?: { explain?: boolean }): QueryBuilderInterface<M>;
|
|
361
|
-
|
|
362
|
-
/**
|
|
363
|
-
* Order rows by nearest-neighbour distance to `vector`, closest first.
|
|
364
|
-
*
|
|
365
|
-
* Postgres only, over a property declared as `type: "vector"`. Each row
|
|
366
|
-
* comes back with a `_distance`. Any `where` on the same query filters
|
|
367
|
-
* before the ordering; distance decides the order.
|
|
368
|
-
*
|
|
369
|
-
* The query embedding is the caller's to produce.
|
|
370
|
-
*/
|
|
371
|
-
vectorSearch(
|
|
372
|
-
property: string,
|
|
373
|
-
vector: number[],
|
|
374
|
-
options?: { distance?: "cosine" | "l2" | "inner_product"; threshold?: number }
|
|
375
|
-
): QueryBuilderInterface<M>;
|
|
376
|
-
include(...relations: string[]): QueryBuilderInterface<M>;
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
// =============================================================================
|
|
380
|
-
// SDK-facing types — flat rows, no Entity wrapper
|
|
381
|
-
// =============================================================================
|
|
382
|
-
|
|
383
|
-
/**
|
|
384
|
-
* Pagination metadata returned with collection queries.
|
|
385
|
-
* @group Data
|
|
386
|
-
*/
|
|
387
|
-
export interface PaginationMeta {
|
|
388
|
-
total: number;
|
|
389
|
-
limit: number;
|
|
390
|
-
offset: number;
|
|
391
|
-
hasMore: boolean;
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
/**
|
|
395
|
-
* Paginated response from a collection query (SDK-facing).
|
|
396
|
-
* Returns flat rows instead of Entity-wrapped objects.
|
|
397
|
-
*
|
|
398
|
-
* @example
|
|
399
|
-
* const { data, meta } = await rebase.data.posts.find();
|
|
400
|
-
* console.log(data[0].title); // direct access — no .values
|
|
401
|
-
* console.log(meta.total);
|
|
402
|
-
*
|
|
403
|
-
* @group Data
|
|
404
|
-
*/
|
|
405
|
-
export interface FindResult<M extends Record<string, unknown> = Record<string, unknown>> {
|
|
406
|
-
/**
|
|
407
|
-
* Flat rows matching the query, each carrying whatever the query computed
|
|
408
|
-
* for it — see {@link QueryComputedFields}.
|
|
409
|
-
*/
|
|
410
|
-
data: (M & QueryComputedFields)[];
|
|
411
|
-
/** Pagination metadata */
|
|
412
|
-
meta: PaginationMeta;
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
/**
|
|
416
|
-
* Values a query attaches to a row that are not columns of it.
|
|
417
|
-
*
|
|
418
|
-
* Both are absent unless the query asked for the thing that produces them, so
|
|
419
|
-
* both are optional — and reading one on a query that did not ask returns
|
|
420
|
-
* `undefined` rather than a wrong number.
|
|
421
|
-
*
|
|
422
|
-
* They live here rather than on the row type because a generated row type
|
|
423
|
-
* describes a *table*, and neither of these is in one. Without this, a caller
|
|
424
|
-
* who sorted by relevance could not then read the relevance.
|
|
425
|
-
*
|
|
426
|
-
* A `type` alias, deliberately, not an `interface`. TypeScript grants an
|
|
427
|
-
* implicit index signature to a type alias and withholds it from an interface,
|
|
428
|
-
* so `Row & QueryComputedFields` stops being assignable to
|
|
429
|
-
* `Record<string, unknown>` the moment this becomes an interface. Seven casts
|
|
430
|
-
* in one downstream app broke on exactly that.
|
|
431
|
-
*
|
|
432
|
-
* @group Data
|
|
433
|
-
*/
|
|
434
|
-
export type QueryComputedFields = {
|
|
435
|
-
/**
|
|
436
|
-
* Relevance, when the collection declares a {@link SearchConfig} and the
|
|
437
|
-
* query carried a search string. Higher is better; the scale is not
|
|
438
|
-
* comparable between two different search strings.
|
|
439
|
-
*/
|
|
440
|
-
_score?: number;
|
|
441
|
-
/**
|
|
442
|
-
* Which declared fields matched, and the text around each hit. Present only
|
|
443
|
-
* when the query asked for it — `.search(term, { explain: true })` — because
|
|
444
|
-
* it costs a `ts_headline` per field per row.
|
|
445
|
-
*/
|
|
446
|
-
_matches?: SearchMatch[];
|
|
447
|
-
/**
|
|
448
|
-
* Distance to the query vector, when the query used
|
|
449
|
-
* {@link FindParams.vectorSearch}. Lower is closer, and the rows are
|
|
450
|
-
* already ordered by it.
|
|
451
|
-
*/
|
|
452
|
-
_distance?: number;
|
|
453
|
-
};
|
|
454
|
-
|
|
455
|
-
/**
|
|
456
|
-
* Which column an iteration seeks on, for keyset ("seek") pagination.
|
|
457
|
-
*
|
|
458
|
-
* Either the column name on its own — sorted ascending — or the column plus an
|
|
459
|
-
* explicit direction. The column must be **unique** and must be the column the
|
|
460
|
-
* query is ordered by; see {@link PageWalkOptions.cursor}.
|
|
461
|
-
*
|
|
462
|
-
* @group Data
|
|
463
|
-
*/
|
|
464
|
-
export type CursorSpec<M extends Record<string, unknown> = Record<string, unknown>> =
|
|
465
|
-
| (Extract<keyof M, string>)
|
|
466
|
-
| { field: Extract<keyof M, string>; direction?: "asc" | "desc" };
|
|
467
|
-
|
|
468
|
-
/**
|
|
469
|
-
* How {@link SDKCollectionClient.iterate} / {@link SDKCollectionClient.findAll}
|
|
470
|
-
* walk a collection, layered on top of the normal `find()` parameters.
|
|
471
|
-
*
|
|
472
|
-
* @group Data
|
|
473
|
-
*/
|
|
474
|
-
export interface PageWalkOptions<M extends Record<string, unknown> = Record<string, unknown>> {
|
|
475
|
-
/**
|
|
476
|
-
* Rows fetched per request. Defaults to 200; values below 1 are clamped up.
|
|
477
|
-
* This is the request size, not a result cap — the iteration keeps going
|
|
478
|
-
* until the server says there is nothing left.
|
|
479
|
-
*/
|
|
480
|
-
pageSize?: number;
|
|
481
|
-
/**
|
|
482
|
-
* Paginate by **seeking on a column** instead of by offset.
|
|
483
|
-
*
|
|
484
|
-
* Offset paging — the default — re-counts rows on every request, so a row
|
|
485
|
-
* inserted or deleted *while the iteration runs* shifts the window and the
|
|
486
|
-
* walk silently skips or repeats rows. Seeking is immune to that: each page
|
|
487
|
-
* asks for rows strictly after the last one seen, so concurrent writes
|
|
488
|
-
* before the cursor cannot move it.
|
|
489
|
-
*
|
|
490
|
-
* Prefer this whenever the collection has a unique, sortable column
|
|
491
|
-
* (typically its primary key). The column must be unique — a repeated value
|
|
492
|
-
* at a page boundary either skips rows or stalls, and the iterator throws
|
|
493
|
-
* rather than looping — and the query is ordered by it, so a `cursor` and a
|
|
494
|
-
* conflicting `orderBy` is an error, not a silent override.
|
|
495
|
-
*
|
|
496
|
-
* Implemented with the parameters `find()` already takes (an `orderBy` plus
|
|
497
|
-
* a `>` / `<` filter on the cursor column), so it works on every transport
|
|
498
|
-
* and needs nothing new from the server.
|
|
499
|
-
*
|
|
500
|
-
* @example
|
|
501
|
-
* for await (const job of client.data.jobs.iterate({ cursor: "id" })) { … }
|
|
502
|
-
*/
|
|
503
|
-
cursor?: CursorSpec<M>;
|
|
504
|
-
/**
|
|
505
|
-
* Hard ceiling on the number of requests one walk may make, so a server
|
|
506
|
-
* that never stops saying `hasMore` cannot spin forever. Defaults to
|
|
507
|
-
* 10 000 pages; hitting it throws.
|
|
508
|
-
*/
|
|
509
|
-
maxPages?: number;
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
/**
|
|
513
|
-
* Parameters accepted by {@link SDKCollectionClient.iterate} — everything
|
|
514
|
-
* `find()` takes except the window itself (`limit`, `offset`, `page`), which
|
|
515
|
-
* the iterator owns, plus the walk options.
|
|
516
|
-
*
|
|
517
|
-
* @group Data
|
|
518
|
-
*/
|
|
519
|
-
export type IterateParams<M extends Record<string, unknown> = Record<string, unknown>> =
|
|
520
|
-
Omit<FindParams<M>, "limit" | "offset" | "page"> & PageWalkOptions<M>;
|
|
521
|
-
|
|
522
|
-
/**
|
|
523
|
-
* Parameters accepted by {@link SDKCollectionClient.findAll}: the iteration
|
|
524
|
-
* parameters plus the ceiling that keeps a whole collection from being pulled
|
|
525
|
-
* into memory unnoticed.
|
|
526
|
-
*
|
|
527
|
-
* @group Data
|
|
528
|
-
*/
|
|
529
|
-
export type FindAllParams<M extends Record<string, unknown> = Record<string, unknown>> =
|
|
530
|
-
IterateParams<M> & {
|
|
531
|
-
/**
|
|
532
|
-
* Most rows to materialise. Defaults to 10 000. Exceeding it **throws**
|
|
533
|
-
* — a truncated array returned as if it were the whole answer is the
|
|
534
|
-
* kind of quiet wrong that shows up months later in a report. Pass
|
|
535
|
-
* `Infinity` to opt out deliberately, or use `iterate()` to stream.
|
|
536
|
-
*/
|
|
537
|
-
maxRows?: number;
|
|
538
|
-
};
|
|
539
|
-
|
|
540
|
-
/**
|
|
541
|
-
* Fluent Query Builder Interface for the SDK client.
|
|
542
|
-
* Returns `FindResult<M>` (flat rows) instead of `FindResponse<M>` (Entity-wrapped).
|
|
543
|
-
*
|
|
544
|
-
* @group Data
|
|
545
|
-
*/
|
|
546
|
-
export interface SDKQueryBuilderInterface<M extends Record<string, unknown> = Record<string, unknown>> {
|
|
547
|
-
where<K extends keyof M & string, Op extends WhereFilterOp>(column: K, operator: Op, value: WhereValueFor<Op, M[K]>): this;
|
|
548
|
-
where(logicalCondition: LogicalCondition): this;
|
|
549
|
-
orderBy(column: (keyof M & string) | ComputedSortField, direction?: "asc" | "desc"): this;
|
|
550
|
-
limit(count: number): this;
|
|
551
|
-
offset(count: number): this;
|
|
552
|
-
search(searchString: string, options?: { explain?: boolean }): this;
|
|
553
|
-
|
|
554
|
-
/**
|
|
555
|
-
* Order rows by nearest-neighbour distance to `vector`, closest first.
|
|
556
|
-
*
|
|
557
|
-
* Postgres only, over a property declared as `type: "vector"`. Each row
|
|
558
|
-
* comes back with a `_distance`. Any `where` on the same query filters
|
|
559
|
-
* before the ordering; distance decides the order.
|
|
560
|
-
*
|
|
561
|
-
* The query embedding is the caller's to produce.
|
|
562
|
-
*/
|
|
563
|
-
vectorSearch(
|
|
564
|
-
property: string,
|
|
565
|
-
vector: number[],
|
|
566
|
-
options?: { distance?: "cosine" | "l2" | "inner_product"; threshold?: number }
|
|
567
|
-
): this;
|
|
568
|
-
include(...relations: string[]): this;
|
|
569
|
-
find(): Promise<FindResult<M>>;
|
|
570
|
-
count(): Promise<number>;
|
|
571
|
-
listen(onUpdate: (data: FindResult<M>) => void, onError?: (error: Error) => void): () => void;
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
/**
|
|
575
|
-
* SDK collection client — returns flat rows, no Entity wrapper.
|
|
576
|
-
*
|
|
577
|
-
* This is the public API surface for app developers using
|
|
578
|
-
* `createRebaseClient()`. admin internals use `CollectionAccessor` instead.
|
|
579
|
-
*
|
|
580
|
-
* Type parameters:
|
|
581
|
-
* - `M` — the **Row** shape returned by reads (`find`, `findById`, `listen`).
|
|
582
|
-
* - `I` — the **Insert** shape accepted by {@link create}. Defaults to
|
|
583
|
-
* `Partial<M>`; the generated SDK supplies a dedicated `Insert` type where
|
|
584
|
-
* required columns are required and auto-generated / read-only columns are
|
|
585
|
-
* omitted, so `create({})` on a table with required fields is a compile error.
|
|
586
|
-
* - `U` — the **Update** shape accepted by {@link update}. Defaults to
|
|
587
|
-
* `Partial<M>`; the generated SDK supplies a dedicated `Update` type.
|
|
588
|
-
*
|
|
589
|
-
* @example
|
|
590
|
-
* const { data: posts } = await rebase.data.posts.find();
|
|
591
|
-
* console.log(posts[0].title); // flat access
|
|
592
|
-
* console.log(posts[0].id); // id at top level
|
|
593
|
-
*
|
|
594
|
-
* const post = await rebase.data.posts.findById(1);
|
|
595
|
-
* console.log(post?.title); // no .values needed
|
|
596
|
-
*
|
|
597
|
-
* @group Data
|
|
598
|
-
*/
|
|
599
|
-
/**
|
|
600
|
-
* Per-request options for a write.
|
|
601
|
-
* @group Data
|
|
602
|
-
*/
|
|
603
|
-
export interface WriteOptions {
|
|
604
|
-
/**
|
|
605
|
-
* Names this write, so re-sending it is recognised instead of repeated.
|
|
606
|
-
*
|
|
607
|
-
* A client that does not see a response cannot know whether the write
|
|
608
|
-
* committed. Retrying is therefore the only option, and without a key the
|
|
609
|
-
* server has no way to tell a retry from a second, genuinely new write — so
|
|
610
|
-
* it performs it again. On a table with a server-assigned id that is a
|
|
611
|
-
* duplicate row, because the id the client chose was never used.
|
|
612
|
-
*
|
|
613
|
-
* A key names **one** request, not a job. It records the method, the path
|
|
614
|
-
* and the body it was claimed for, so re-sending that exact request replays
|
|
615
|
-
* its answer, while the same key on a different one is refused with
|
|
616
|
-
* `IDEMPOTENCY_KEY_REUSED` (422) rather than silently answered with the
|
|
617
|
-
* first request's result. Pass a fresh key — a uuid — per call; a reusable
|
|
618
|
-
* business id shared by the create and the delete of one import means the
|
|
619
|
-
* second of them never runs.
|
|
620
|
-
*
|
|
621
|
-
* Set by the offline queue on every replay. Honoured for 24 hours and scoped
|
|
622
|
-
* to the authenticated user — an unauthenticated caller has no principal to
|
|
623
|
-
* scope it to, so the key is ignored there. A retry sent while the first
|
|
624
|
-
* attempt is still being answered gets `IDEMPOTENCY_KEY_IN_PROGRESS` (409)
|
|
625
|
-
* and should be sent again. A server that cannot store keys ignores the
|
|
626
|
-
* header rather than refusing the write.
|
|
627
|
-
*/
|
|
628
|
-
idempotencyKey?: string;
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
export interface SDKCollectionClient<
|
|
632
|
-
M extends Record<string, unknown> = Record<string, unknown>,
|
|
633
|
-
I = Partial<M>,
|
|
634
|
-
U = Partial<M>
|
|
635
|
-
> {
|
|
636
|
-
/**
|
|
637
|
-
* Find multiple records with optional filtering, pagination, and sorting.
|
|
638
|
-
*/
|
|
639
|
-
find(params?: FindParams<M>): Promise<FindResult<M>>;
|
|
640
|
-
|
|
641
|
-
/**
|
|
642
|
-
* Walk every record matching a query, one row at a time, fetching pages as
|
|
643
|
-
* the consumer consumes them.
|
|
644
|
-
*
|
|
645
|
-
* This is the pagination primitive: `find()` returns one window, `iterate()`
|
|
646
|
-
* returns all of them without the caller hand-rolling the
|
|
647
|
-
* `limit` / `offset += ` / "am I done yet" loop. Nothing is buffered — rows
|
|
648
|
-
* are yielded as each page arrives, so a million-row walk costs one page of
|
|
649
|
-
* memory. `break` stops the walk and no further requests are made.
|
|
650
|
-
*
|
|
651
|
-
* Termination is driven by the server's `meta.hasMore`, never by comparing
|
|
652
|
-
* a page's length against the requested limit — a final page that happens
|
|
653
|
-
* to be exactly full is indistinguishable that way, and a walk that stops
|
|
654
|
-
* there drops rows. An empty page also ends the walk, and
|
|
655
|
-
* {@link PageWalkOptions.maxPages} bounds a server that never stops saying
|
|
656
|
-
* there is more.
|
|
657
|
-
*
|
|
658
|
-
* ## Consistency
|
|
659
|
-
*
|
|
660
|
-
* By default this pages by **offset**, which is only as stable as the table
|
|
661
|
-
* is still: a row inserted or deleted ahead of the cursor between two
|
|
662
|
-
* requests shifts every later window, so the walk can skip a row or hand
|
|
663
|
-
* back the same one twice. That is inherent to offset paging, not a bug
|
|
664
|
-
* here. On a collection with a unique sortable column, pass
|
|
665
|
-
* {@link PageWalkOptions.cursor} to seek on it instead — the walk then
|
|
666
|
-
* asks for rows strictly after the last one it saw, which concurrent writes
|
|
667
|
-
* cannot perturb.
|
|
668
|
-
*
|
|
669
|
-
* @example
|
|
670
|
-
* for await (const job of client.data.jobs.iterate({
|
|
671
|
-
* where: { status: ["==", "queued"] },
|
|
672
|
-
* cursor: "id",
|
|
673
|
-
* pageSize: 500
|
|
674
|
-
* })) {
|
|
675
|
-
* await handle(job);
|
|
676
|
-
* }
|
|
677
|
-
*/
|
|
678
|
-
iterate(params?: IterateParams<M>): AsyncIterableIterator<M>;
|
|
679
|
-
|
|
680
|
-
/**
|
|
681
|
-
* {@link iterate}, collected into an array.
|
|
682
|
-
*
|
|
683
|
-
* Convenient when the result is known to be small and awkward to stream.
|
|
684
|
-
* Because "known to be small" is an assumption and not a fact, the result is
|
|
685
|
-
* capped — 10 000 rows by default — and going over the cap **throws**
|
|
686
|
-
* rather than returning a short array that reads like a complete one. Raise
|
|
687
|
-
* {@link FindAllParams.maxRows} when the data really is bigger, or switch to
|
|
688
|
-
* `iterate()` and stream it.
|
|
689
|
-
*
|
|
690
|
-
* The offset-drift caveat on {@link iterate} applies here too.
|
|
691
|
-
*
|
|
692
|
-
* @throws When more rows match than `maxRows` allows.
|
|
693
|
-
*
|
|
694
|
-
* @example
|
|
695
|
-
* const overdue = await client.data.invoices.findAll({
|
|
696
|
-
* where: { due_at: ["<", today] },
|
|
697
|
-
* cursor: "id"
|
|
698
|
-
* });
|
|
699
|
-
*/
|
|
700
|
-
findAll(params?: FindAllParams<M>): Promise<M[]>;
|
|
701
|
-
|
|
702
|
-
/**
|
|
703
|
-
* Find a single record by its ID.
|
|
704
|
-
*/
|
|
705
|
-
findById(id: string | number): Promise<M | undefined>;
|
|
706
|
-
|
|
707
|
-
/**
|
|
708
|
-
* Create a new record.
|
|
709
|
-
* @param data The record data to create (the collection's `Insert` shape).
|
|
710
|
-
* @param id Optional specific id, sent as an `id` column. This is for tables
|
|
711
|
-
* whose key *is* `id`: the value goes in as that column. For a table keyed
|
|
712
|
-
* on anything else (a `sku`, a composite key), there is no `id` column to
|
|
713
|
-
* receive it — put the key in `data` instead, where it belongs among the
|
|
714
|
-
* columns.
|
|
715
|
-
* @returns The created row
|
|
716
|
-
*/
|
|
717
|
-
create(data: I, id?: string | number, options?: WriteOptions): Promise<M>;
|
|
718
|
-
|
|
719
|
-
/**
|
|
720
|
-
* Write many records in a single request and a single transaction.
|
|
721
|
-
*
|
|
722
|
-
* Built for imports and ETL, where one call per row means one HTTP round
|
|
723
|
-
* trip and one transaction per row. Every record still runs the normal
|
|
724
|
-
* pipeline — callbacks, relations, row-level security — and the batch is
|
|
725
|
-
* all-or-nothing: if any record is rejected, none of them land and the
|
|
726
|
-
* error names the offending index.
|
|
727
|
-
*
|
|
728
|
-
* A record carrying its primary key updates that row; one without inserts.
|
|
729
|
-
* With `{ upsert: true }` each record is written as INSERT ... ON CONFLICT
|
|
730
|
-
* DO UPDATE on the primary key instead, which is what makes a re-runnable
|
|
731
|
-
* import idempotent.
|
|
732
|
-
*
|
|
733
|
-
* Batches are capped server-side (1000 rows by default) because one batch
|
|
734
|
-
* holds its locks for the whole transaction — chunk larger jobs.
|
|
735
|
-
*
|
|
736
|
-
* Pass {@link WriteOptions.idempotencyKey} on anything that may be retried.
|
|
737
|
-
* A client that never sees the response cannot know whether the batch
|
|
738
|
-
* committed, and without a key the server cannot tell the retry from a
|
|
739
|
-
* second genuine import — so it performs it again, duplicating every row in
|
|
740
|
-
* the batch rather than just one.
|
|
741
|
-
*
|
|
742
|
-
* @returns The written rows, in the order given.
|
|
743
|
-
*
|
|
744
|
-
* @example
|
|
745
|
-
* ```ts
|
|
746
|
-
* for (const chunk of chunks(rows, 1000)) {
|
|
747
|
-
* await client.data.products.createMany(chunk, { upsert: true });
|
|
748
|
-
* }
|
|
749
|
-
* ```
|
|
750
|
-
*/
|
|
751
|
-
createMany(data: I[], options?: { upsert?: boolean } & WriteOptions): Promise<M[]>;
|
|
752
|
-
|
|
753
|
-
/**
|
|
754
|
-
* Update an existing record by ID.
|
|
755
|
-
* @param data The fields to update (the collection's `Update` shape).
|
|
756
|
-
* @returns The updated row.
|
|
757
|
-
* @throws {RebaseApiError} with status 404 when the record does not exist.
|
|
758
|
-
*/
|
|
759
|
-
update(id: string | number, data: U): Promise<M>;
|
|
760
|
-
|
|
761
|
-
/**
|
|
762
|
-
* Update many records in a single request and a single transaction.
|
|
763
|
-
*
|
|
764
|
-
* The counterpart to {@link createMany}, and the reason it exists is the
|
|
765
|
-
* same: one call per row means one HTTP round trip and one transaction per
|
|
766
|
-
* row. Every record still runs the normal pipeline — callbacks, relations,
|
|
767
|
-
* row-level security — and the batch is all-or-nothing, so a rejected
|
|
768
|
-
* record leaves none of them written and the error names the offending
|
|
769
|
-
* index.
|
|
770
|
-
*
|
|
771
|
-
* Each entry is `{ id, data }` rather than a flat row carrying its own key.
|
|
772
|
-
* That is deliberate: on a table keyed on something other than `id` — a
|
|
773
|
-
* `sku`, a composite key — a flat row cannot say whether a column is the
|
|
774
|
-
* address or a value to write. Naming the address separately mirrors
|
|
775
|
-
* single-row `update(id, data)` exactly and leaves nothing to infer.
|
|
776
|
-
*
|
|
777
|
-
* An id that matches no row fails the batch with a 404 rather than being
|
|
778
|
-
* skipped, for the same reason `update()` does: silently updating four of
|
|
779
|
-
* five rows is worse than updating none.
|
|
780
|
-
*
|
|
781
|
-
* Batches share `createMany`'s server-side cap (1000 rows by default),
|
|
782
|
-
* because one batch holds its locks for the whole transaction.
|
|
783
|
-
*
|
|
784
|
-
* Pass {@link WriteOptions.idempotencyKey} on anything that may be retried.
|
|
785
|
-
* An update replayed in full is naturally idempotent, but one interleaved
|
|
786
|
-
* with another writer's is not — the key is what stops a lost ACK from
|
|
787
|
-
* re-applying a stale batch over newer data.
|
|
788
|
-
*
|
|
789
|
-
* @returns The updated rows, in the order given.
|
|
790
|
-
*
|
|
791
|
-
* @example
|
|
792
|
-
* ```ts
|
|
793
|
-
* await client.data.orders.updateMany([
|
|
794
|
-
* { id: "o-1", data: { status: "shipped" } },
|
|
795
|
-
* { id: "o-2", data: { status: "shipped" } }
|
|
796
|
-
* ]);
|
|
797
|
-
* ```
|
|
798
|
-
*/
|
|
799
|
-
updateMany(updates: { id: string | number; data: U }[], options?: WriteOptions): Promise<M[]>;
|
|
800
|
-
|
|
801
|
-
/**
|
|
802
|
-
* Delete a record by ID.
|
|
803
|
-
* @throws {RebaseApiError} with status 404 when the record does not exist.
|
|
804
|
-
*/
|
|
805
|
-
delete(id: string | number): Promise<void>;
|
|
806
|
-
|
|
807
|
-
/**
|
|
808
|
-
* Delete many records in a single request and a single transaction.
|
|
809
|
-
*
|
|
810
|
-
* Takes ids, not a filter. A filter-shaped bulk delete is a different and
|
|
811
|
-
* far more dangerous operation — the failure mode is an omitted or
|
|
812
|
-
* mistyped condition emptying a table, and it cannot be reviewed at the
|
|
813
|
-
* call site the way an explicit list can. Read first, then pass the ids you
|
|
814
|
-
* meant.
|
|
815
|
-
*
|
|
816
|
-
* `beforeDelete` and `afterDelete` fire per row, exactly as they do for
|
|
817
|
-
* single deletes, and returning `false` from `beforeDelete` fails the batch
|
|
818
|
-
* rather than quietly dropping one row from it. All-or-nothing, so an id
|
|
819
|
-
* that matches no row 404s the whole call.
|
|
820
|
-
*
|
|
821
|
-
* Shares `createMany`'s row cap.
|
|
822
|
-
*
|
|
823
|
-
* @example
|
|
824
|
-
* ```ts
|
|
825
|
-
* const stale = await client.data.sessions.findAll({
|
|
826
|
-
* where: { expires_at: ["<", cutoff] }
|
|
827
|
-
* });
|
|
828
|
-
* await client.data.sessions.deleteMany(stale.map(s => s.id as string));
|
|
829
|
-
* ```
|
|
830
|
-
*/
|
|
831
|
-
deleteMany(ids: (string | number)[], options?: WriteOptions): Promise<void>;
|
|
832
|
-
|
|
833
|
-
/**
|
|
834
|
-
* The low-level realtime subscription: raw server pushes, nothing else.
|
|
835
|
-
*
|
|
836
|
-
* **Prefer `observe()`** on a client from `@rebasepro/client`, which wraps
|
|
837
|
-
* this one and is what a UI actually wants — it emits from the local
|
|
838
|
-
* database first when offline is enabled, re-emits on local writes and
|
|
839
|
-
* rollbacks, and de-duplicates emissions so a refresh that changes nothing
|
|
840
|
-
* does not call back. `listen` does none of that; it forwards what the
|
|
841
|
-
* socket sends.
|
|
842
|
-
*
|
|
843
|
-
* Optional because it is only present when realtime is enabled. `observe()`
|
|
844
|
-
* is not — it degrades to a single fetch — which is the other reason to
|
|
845
|
-
* reach for it instead.
|
|
846
|
-
*/
|
|
847
|
-
listen?(params: FindParams<M> | undefined, onUpdate: (response: FindResult<M>) => void, onError?: (error: Error) => void): () => void;
|
|
848
|
-
|
|
849
|
-
/** {@link listen} for a single row. Prefer `observeById()`. */
|
|
850
|
-
listenById?(id: string | number, onUpdate: (row: M | undefined) => void, onError?: (error: Error) => void): () => void;
|
|
851
|
-
|
|
852
|
-
/**
|
|
853
|
-
* Count the number of records matching the given filter.
|
|
854
|
-
*/
|
|
855
|
-
count?(params?: FindParams<M>): Promise<number>;
|
|
856
|
-
|
|
857
|
-
// Fluent Query Builder
|
|
858
|
-
where<K extends keyof M & string, Op extends WhereFilterOp>(column: K, operator: Op, value: WhereValueFor<Op, M[K]>): SDKQueryBuilderInterface<M>;
|
|
859
|
-
where(logicalCondition: LogicalCondition): SDKQueryBuilderInterface<M>;
|
|
860
|
-
orderBy(column: (keyof M & string) | ComputedSortField, direction?: "asc" | "desc"): SDKQueryBuilderInterface<M>;
|
|
861
|
-
limit(count: number): SDKQueryBuilderInterface<M>;
|
|
862
|
-
offset(count: number): SDKQueryBuilderInterface<M>;
|
|
863
|
-
search(searchString: string, options?: { explain?: boolean }): SDKQueryBuilderInterface<M>;
|
|
864
|
-
/**
|
|
865
|
-
* Order rows by nearest-neighbour distance to `vector`, closest first.
|
|
866
|
-
* Postgres only, over a `type: "vector"` property. See
|
|
867
|
-
* {@link SDKQueryBuilderInterface.vectorSearch}.
|
|
868
|
-
*/
|
|
869
|
-
vectorSearch(
|
|
870
|
-
property: string,
|
|
871
|
-
vector: number[],
|
|
872
|
-
options?: { distance?: "cosine" | "l2" | "inner_product"; threshold?: number }
|
|
873
|
-
): SDKQueryBuilderInterface<M>;
|
|
874
|
-
include(...relations: string[]): SDKQueryBuilderInterface<M>;
|
|
875
|
-
}
|
|
876
|
-
|
|
877
|
-
/**
|
|
878
|
-
* The unified data access object for the **admin panel** (Entity-shaped).
|
|
879
|
-
*
|
|
880
|
-
* Access collections as dynamic properties: `data.products.find(...)`. Each
|
|
881
|
-
* accessor returns `Entity`-wrapped records (`{ id, path, values }`) — the
|
|
882
|
-
* view-model the admin renders. This is what `useData()` / the admin
|
|
883
|
-
* `RebaseContext.data` are backed by.
|
|
884
|
-
*
|
|
885
|
-
* @internal App developers do **not** use this — they use
|
|
886
|
-
* {@link RebaseSdkData} (flat rows), which is what the SDK client and backend
|
|
887
|
-
* `context.data` expose. This Entity-shaped map backs the admin panel only.
|
|
888
|
-
*
|
|
889
|
-
* @group Data
|
|
890
|
-
*/
|
|
891
|
-
export type RebaseData<DB = unknown> = {
|
|
892
|
-
/**
|
|
893
|
-
* Get a collection accessor by slug.
|
|
894
|
-
* Alternative to dynamic property access for cases where
|
|
895
|
-
* the collection name is a variable.
|
|
896
|
-
*
|
|
897
|
-
* @example
|
|
898
|
-
* const accessor = data.collection("products");
|
|
899
|
-
* await accessor.find({ limit: 10 });
|
|
900
|
-
*/
|
|
901
|
-
collection<M extends Record<string, unknown> = Record<string, unknown>>(slug: string): CollectionAccessor<M>;
|
|
902
|
-
} & (
|
|
903
|
-
DB extends Record<string, unknown>
|
|
904
|
-
? { [K in keyof DB]: CollectionAccessor<DB[K] extends { Row: infer R extends Record<string, unknown> } ? R : Record<string, unknown>> }
|
|
905
|
-
: {
|
|
906
|
-
/**
|
|
907
|
-
* Dynamic collection accessor.
|
|
908
|
-
* Access any collection by its slug as a property.
|
|
909
|
-
*
|
|
910
|
-
* The index signature is `CollectionAccessor` alone, for the reason
|
|
911
|
-
* spelled out on {@link RebaseSdkData}: unioning in the `collection`
|
|
912
|
-
* method's own signature is unnecessary across an intersection, and it
|
|
913
|
-
* costs `data.products.find()` — the access this `@example` documents.
|
|
914
|
-
*
|
|
915
|
-
* @example
|
|
916
|
-
* data.products.find({ where: { status: ["==", "published"] } })
|
|
917
|
-
*/
|
|
918
|
-
[collectionSlug: string]: CollectionAccessor;
|
|
919
|
-
}
|
|
920
|
-
);
|
|
921
|
-
|
|
922
|
-
/**
|
|
923
|
-
* The unified data access object for the **SDK** — flat rows, no Entity wrapper.
|
|
924
|
-
*
|
|
925
|
-
* This is the symmetric developer-facing data API, identical in shape on both
|
|
926
|
-
* sides of the stack:
|
|
927
|
-
* - The frontend SDK client (`client.data.products.find()`)
|
|
928
|
-
* - Backend framework callbacks & scripts (`context.data.products.find()`)
|
|
929
|
-
*
|
|
930
|
-
* Every accessor returns flat rows (the table's columns) via
|
|
931
|
-
* {@link SDKCollectionClient} — access fields directly (`row.title`), never
|
|
932
|
-
* `row.values.title`. The admin uses {@link RebaseData} (Entity) instead.
|
|
933
|
-
*
|
|
934
|
-
* @example
|
|
935
|
-
* // Frontend SDK
|
|
936
|
-
* const { data: posts } = await client.data.posts.find();
|
|
937
|
-
* console.log(posts[0].title); // flat — no .values
|
|
938
|
-
*
|
|
939
|
-
* // Backend callback — identical shape
|
|
940
|
-
* callbacks: {
|
|
941
|
-
* beforeSave: async ({ context }) => {
|
|
942
|
-
* const product = await context.data.products.findById(id);
|
|
943
|
-
* console.log(product?.price); // flat — no .values
|
|
944
|
-
* }
|
|
945
|
-
* }
|
|
946
|
-
*
|
|
947
|
-
* @group Data
|
|
948
|
-
*/
|
|
949
|
-
/**
|
|
950
|
-
* Extract the `Row` shape from a generated `Database[slug]` entry, falling
|
|
951
|
-
* back to an open record when the entry is untyped.
|
|
952
|
-
* @group Data
|
|
953
|
-
*/
|
|
954
|
-
export type RowOf<T> = T extends { Row: infer R extends Record<string, unknown> } ? R : Record<string, unknown>;
|
|
955
|
-
|
|
956
|
-
/**
|
|
957
|
-
* Extract the `Insert` shape from a generated `Database[slug]` entry (the
|
|
958
|
-
* input accepted by `create`), falling back to `Partial<Row>`.
|
|
959
|
-
* @group Data
|
|
960
|
-
*/
|
|
961
|
-
export type InsertOf<T> = T extends { Insert: infer I extends Record<string, unknown> } ? I : Partial<RowOf<T>>;
|
|
962
|
-
|
|
963
|
-
/**
|
|
964
|
-
* Extract the `Update` shape from a generated `Database[slug]` entry (the
|
|
965
|
-
* input accepted by `update`), falling back to `Partial<Row>`.
|
|
966
|
-
* @group Data
|
|
967
|
-
*/
|
|
968
|
-
export type UpdateOf<T> = T extends { Update: infer U extends Record<string, unknown> } ? U : Partial<RowOf<T>>;
|
|
969
|
-
|
|
970
|
-
/**
|
|
971
|
-
* Note on the untyped branch below: its index signature is
|
|
972
|
-
* `SDKCollectionClient`, NOT `SDKCollectionClient | ((slug: string) => …)`.
|
|
973
|
-
*
|
|
974
|
-
* The union looks like it is needed so `collection` — a method on this same
|
|
975
|
-
* object — satisfies the index signature. It is not, because `collection` is
|
|
976
|
-
* declared in a *separate* member of the intersection, and TypeScript only
|
|
977
|
-
* requires named properties to be assignable to an index signature declared
|
|
978
|
-
* alongside them. Including the function arm cost the documented accessor:
|
|
979
|
-
*
|
|
980
|
-
* rebase.dataAsAdmin.projects.find()
|
|
981
|
-
* // ^ Property 'find' does not exist on type
|
|
982
|
-
* // 'SDKCollectionClient | ((slug: string) => …)'
|
|
983
|
-
*
|
|
984
|
-
* Every project without a generated `Database` type lands on this branch, so
|
|
985
|
-
* property-style access — the form used by the `@example` below, by the
|
|
986
|
-
* scaffolded function template, and by the 0.13 migration note — did not
|
|
987
|
-
* compile for any of them. Do not restore the arm; use `collection(slug)` if a
|
|
988
|
-
* caller genuinely needs the by-slug function.
|
|
989
|
-
*/
|
|
990
|
-
export type RebaseSdkData<DB = unknown> = {
|
|
991
|
-
/**
|
|
992
|
-
* Get a flat collection accessor by slug.
|
|
993
|
-
*
|
|
994
|
-
* @example
|
|
995
|
-
* const accessor = data.collection("products");
|
|
996
|
-
* await accessor.find({ limit: 10 });
|
|
997
|
-
*/
|
|
998
|
-
collection<M extends Record<string, unknown> = Record<string, unknown>>(slug: string): SDKCollectionClient<M>;
|
|
999
|
-
} & (
|
|
1000
|
-
DB extends Record<string, unknown>
|
|
1001
|
-
? { [K in keyof DB]: SDKCollectionClient<RowOf<DB[K]>, InsertOf<DB[K]>, UpdateOf<DB[K]>> }
|
|
1002
|
-
: {
|
|
1003
|
-
/**
|
|
1004
|
-
* Dynamic flat collection accessor.
|
|
1005
|
-
* Access any collection by its slug as a property.
|
|
1006
|
-
*
|
|
1007
|
-
* @example
|
|
1008
|
-
* data.products.find({ where: { status: ["==", "published"] } })
|
|
1009
|
-
*/
|
|
1010
|
-
[collectionSlug: string]: SDKCollectionClient;
|
|
1011
|
-
}
|
|
1012
|
-
);
|