@rebasepro/types 0.13.0 → 0.13.1-canary.g249daa1
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/dist/call_context.d.ts +61 -4
- package/dist/controllers/client.d.ts +16 -59
- package/dist/controllers/data.d.ts +298 -30
- package/dist/controllers/data_driver.d.ts +48 -0
- package/dist/errors.d.ts +30 -4
- package/dist/index.es.js +113 -5
- package/dist/index.es.js.map +1 -1
- package/dist/types/admin_block.d.ts +1 -1
- package/dist/types/backend.d.ts +2 -0
- package/dist/types/collections.d.ts +36 -3
- package/dist/types/cron.d.ts +50 -9
- package/dist/types/entities.d.ts +11 -0
- package/dist/types/entity_callbacks.d.ts +2 -1
- package/dist/types/index.d.ts +2 -0
- package/dist/types/policy.d.ts +13 -13
- package/dist/types/properties.d.ts +22 -4
- package/dist/types/rls-functions.d.ts +84 -0
- package/dist/types/search.d.ts +231 -0
- package/package.json +2 -2
- package/src/call_context.ts +59 -4
- package/src/controllers/client.ts +16 -80
- package/src/controllers/data.ts +298 -30
- package/src/controllers/data_driver.ts +49 -0
- package/src/errors.ts +43 -4
- package/src/types/admin_block.ts +2 -0
- package/src/types/backend.ts +2 -0
- package/src/types/collections.ts +37 -3
- package/src/types/cron.ts +51 -9
- package/src/types/entities.ts +12 -0
- package/src/types/entity_callbacks.ts +2 -1
- package/src/types/index.ts +2 -0
- package/src/types/policy.ts +13 -13
- package/src/types/properties.ts +23 -4
- package/src/types/rls-functions.ts +98 -0
- package/src/types/search.ts +247 -0
package/src/controllers/data.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { VectorSearchParams } from "./data_driver";
|
|
2
|
+
import type { ComputedSortField, SearchMatch } from "../types/search";
|
|
1
3
|
import { Entity, EntityValues } from "../types/entities";
|
|
2
4
|
import { WhereFilterOp, FieldPath, FilterValues, OrderByTuple } from "../types/filter-operators";
|
|
3
5
|
|
|
@@ -38,13 +40,19 @@ export interface FilterCondition {
|
|
|
38
40
|
*
|
|
39
41
|
* `limit`/`offset` and `page` describe the same window two ways. If **both
|
|
40
42
|
* `offset` and `page` are provided, `page` wins** — the backend computes
|
|
41
|
-
* `offset = (page - 1) * (limit ??
|
|
42
|
-
* Pick one style per query.
|
|
43
|
+
* `offset = (page - 1) * (limit ?? DEFAULT_LIST_LIMIT)` and ignores the
|
|
44
|
+
* explicit `offset`. Pick one style per query.
|
|
43
45
|
*
|
|
44
46
|
* @group Data
|
|
45
47
|
*/
|
|
46
48
|
export interface FindParams<M extends Record<string, unknown> = Record<string, unknown>> {
|
|
47
|
-
/**
|
|
49
|
+
/**
|
|
50
|
+
* Maximum number of items to return.
|
|
51
|
+
*
|
|
52
|
+
* Defaults to {@link DEFAULT_LIST_LIMIT}, and is clamped to
|
|
53
|
+
* {@link MAX_LIST_LIMIT}. Both bounds are applied by the backend, so a
|
|
54
|
+
* read is never unbounded whether or not a limit was asked for.
|
|
55
|
+
*/
|
|
48
56
|
limit?: number;
|
|
49
57
|
/**
|
|
50
58
|
* Number of items to skip. Ignored when {@link FindParams.page} is also
|
|
@@ -53,7 +61,7 @@ export interface FindParams<M extends Record<string, unknown> = Record<string, u
|
|
|
53
61
|
offset?: number;
|
|
54
62
|
/**
|
|
55
63
|
* Page number (1-indexed), alternative to {@link FindParams.offset}.
|
|
56
|
-
* When set, overrides `offset` as `(page - 1) * (limit ??
|
|
64
|
+
* When set, overrides `offset` as `(page - 1) * (limit ?? DEFAULT_LIST_LIMIT)`.
|
|
57
65
|
*/
|
|
58
66
|
page?: number;
|
|
59
67
|
/**
|
|
@@ -80,7 +88,7 @@ export interface FindParams<M extends Record<string, unknown> = Record<string, u
|
|
|
80
88
|
* Sort order as a `[field, direction]` tuple.
|
|
81
89
|
* @example orderBy: ["created_at", "desc"]
|
|
82
90
|
*/
|
|
83
|
-
orderBy?: OrderByTuple<FieldPath<M
|
|
91
|
+
orderBy?: OrderByTuple<FieldPath<M> | ComputedSortField>;
|
|
84
92
|
/**
|
|
85
93
|
* Relations to include in the response.
|
|
86
94
|
*
|
|
@@ -90,11 +98,47 @@ export interface FindParams<M extends Record<string, unknown> = Record<string, u
|
|
|
90
98
|
*/
|
|
91
99
|
include?: string[];
|
|
92
100
|
/**
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
101
|
+
* Text search string, AND-ed with `where`/`logical`. This is the value
|
|
102
|
+
* behind the query builder's `.search()` method.
|
|
103
|
+
*
|
|
104
|
+
* What it compiles to depends on the collection. By default — matching
|
|
105
|
+
* every collection that has not said otherwise — it is a case-insensitive
|
|
106
|
+
* substring match OR-ed across the collection's top-level `string`
|
|
107
|
+
* properties: it does not reach inside `map` or `array` properties, it does
|
|
108
|
+
* not stem or rank, and it cannot use an index.
|
|
109
|
+
*
|
|
110
|
+
* A Postgres collection that declares a `search` block instead gets a
|
|
111
|
+
* ranked full-text match over exactly the fields it named, and rows come
|
|
112
|
+
* back with a {@link FindParams.orderBy}-able `_score`.
|
|
96
113
|
*/
|
|
97
114
|
searchString?: string;
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Nearest-neighbour search over a `vector` property.
|
|
118
|
+
*
|
|
119
|
+
* Postgres only, and only for a collection that declares a property of
|
|
120
|
+
* type `vector`. Rows come back ordered by distance, closest first, each
|
|
121
|
+
* carrying a `_distance`. Combines with `where` and `logical`, which are
|
|
122
|
+
* applied as filters before the ordering — so this is "the nearest rows
|
|
123
|
+
* that also match", not "the nearest rows, then filtered".
|
|
124
|
+
*
|
|
125
|
+
* Supplying the query vector is the caller's job: rebase stores and
|
|
126
|
+
* searches embeddings, it does not compute them.
|
|
127
|
+
*/
|
|
128
|
+
vectorSearch?: VectorSearchParams;
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Ask each returned row to explain itself: which declared search fields
|
|
132
|
+
* matched, with a highlighted snippet from each. Populates `_matches`.
|
|
133
|
+
*
|
|
134
|
+
* Off by default because it is not free — one `ts_headline` per declared
|
|
135
|
+
* field per returned row, and `ts_headline` re-parses the document rather
|
|
136
|
+
* than reading the index. Fine for a page of results, not for an export.
|
|
137
|
+
*
|
|
138
|
+
* Ignored unless the collection declares a `search` block and the query
|
|
139
|
+
* carries a `searchString`; there is nothing to explain otherwise.
|
|
140
|
+
*/
|
|
141
|
+
searchExplain?: boolean;
|
|
98
142
|
}
|
|
99
143
|
|
|
100
144
|
/**
|
|
@@ -116,35 +160,50 @@ export interface FindResponse<M extends Record<string, unknown> = Record<string,
|
|
|
116
160
|
|
|
117
161
|
|
|
118
162
|
/**
|
|
119
|
-
* Fluent query builder for the **admin
|
|
163
|
+
* Fluent query builder for the **admin panel** — resolves to `FindResponse<M>`
|
|
120
164
|
* (Snapshot-wrapped rows).
|
|
121
165
|
*
|
|
122
166
|
* @internal App developers should use {@link SDKQueryBuilderInterface}
|
|
123
167
|
* (flat rows, returned by `client.data.*` / `context.data.*`). This
|
|
124
|
-
* Snapshot-flavored variant backs the admin
|
|
168
|
+
* Snapshot-flavored variant backs the admin panel internals only.
|
|
125
169
|
*
|
|
126
170
|
* @group Data
|
|
127
171
|
*/
|
|
128
172
|
export interface QueryBuilderInterface<M extends Record<string, unknown> = Record<string, unknown>> {
|
|
129
173
|
where<K extends keyof M & string>(column: K, operator: WhereFilterOp, value: WhereValue<M[K]>): this;
|
|
130
174
|
where(logicalCondition: LogicalCondition): this;
|
|
131
|
-
orderBy(column: keyof M & string, direction?: "asc" | "desc"): this;
|
|
175
|
+
orderBy(column: (keyof M & string) | ComputedSortField, direction?: "asc" | "desc"): this;
|
|
132
176
|
limit(count: number): this;
|
|
133
177
|
offset(count: number): this;
|
|
134
|
-
search(searchString: string): this;
|
|
178
|
+
search(searchString: string, options?: { explain?: boolean }): this;
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Order rows by nearest-neighbour distance to `vector`, closest first.
|
|
182
|
+
*
|
|
183
|
+
* Postgres only, over a property declared as `type: "vector"`. Each row
|
|
184
|
+
* comes back with a `_distance`. Any `where` on the same query filters
|
|
185
|
+
* before the ordering; distance decides the order.
|
|
186
|
+
*
|
|
187
|
+
* The query embedding is the caller's to produce.
|
|
188
|
+
*/
|
|
189
|
+
vectorSearch(
|
|
190
|
+
property: string,
|
|
191
|
+
vector: number[],
|
|
192
|
+
options?: { distance?: "cosine" | "l2" | "inner_product"; threshold?: number }
|
|
193
|
+
): this;
|
|
135
194
|
include(...relations: string[]): this;
|
|
136
195
|
find(): Promise<FindResponse<M>>;
|
|
137
196
|
listen(onUpdate: (data: FindResponse<M>) => void, onError?: (error: Error) => void): () => void;
|
|
138
197
|
}
|
|
139
198
|
|
|
140
199
|
/**
|
|
141
|
-
* A single collection's CRUD accessor for the **admin
|
|
200
|
+
* A single collection's CRUD accessor for the **admin panel** — every method
|
|
142
201
|
* resolves to `Snapshot`-wrapped rows (`FindResponse<M>` / `Snapshot<M>`).
|
|
143
202
|
*
|
|
144
203
|
* @internal App developers do **not** use this. The public, symmetric surface
|
|
145
204
|
* is {@link SDKCollectionClient} (flat rows), exposed as `client.data.products`
|
|
146
205
|
* in the SDK and `context.data.products` in framework callbacks. This
|
|
147
|
-
* Snapshot-flavored accessor backs the admin
|
|
206
|
+
* Snapshot-flavored accessor backs the admin panel view-model only.
|
|
148
207
|
*
|
|
149
208
|
* @group Data
|
|
150
209
|
*/
|
|
@@ -181,6 +240,20 @@ export interface CollectionAccessor<M extends Record<string, unknown> = Record<s
|
|
|
181
240
|
*/
|
|
182
241
|
update(id: string | number, data: Partial<EntityValues<M>>): Promise<Entity<M>>;
|
|
183
242
|
|
|
243
|
+
/**
|
|
244
|
+
* Update many records in a single transaction.
|
|
245
|
+
*
|
|
246
|
+
* See {@link SDKCollectionClient.updateMany}. Optional, as `createMany` is.
|
|
247
|
+
*/
|
|
248
|
+
updateMany?(updates: { id: string | number; data: Partial<EntityValues<M>> }[]): Promise<Entity<M>[]>;
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Delete many records in a single transaction.
|
|
252
|
+
*
|
|
253
|
+
* See {@link SDKCollectionClient.deleteMany}. Optional, as `createMany` is.
|
|
254
|
+
*/
|
|
255
|
+
deleteMany?(ids: (string | number)[]): Promise<void>;
|
|
256
|
+
|
|
184
257
|
/**
|
|
185
258
|
* Delete a record by ID.
|
|
186
259
|
*/
|
|
@@ -200,16 +273,37 @@ export interface CollectionAccessor<M extends Record<string, unknown> = Record<s
|
|
|
200
273
|
|
|
201
274
|
/**
|
|
202
275
|
* Count the number of records matching the given filter.
|
|
276
|
+
*
|
|
277
|
+
* Optional on this contract because a data source need not support it, and
|
|
278
|
+
* required on `CollectionClient` — the HTTP implementation always has it.
|
|
279
|
+
* So `client.data.posts.count()` compiles in the browser while the same
|
|
280
|
+
* call through a `context.data` accessor needs `count?.()`, which is the
|
|
281
|
+
* one place the two halves of this API are not interchangeable.
|
|
203
282
|
*/
|
|
204
283
|
count?(params?: FindParams<M>): Promise<number>;
|
|
205
284
|
|
|
206
285
|
// Fluent Query Builder
|
|
207
286
|
where<K extends keyof M & string>(column: K, operator: WhereFilterOp, value: WhereValue<M[K]>): QueryBuilderInterface<M>;
|
|
208
287
|
where(logicalCondition: LogicalCondition): QueryBuilderInterface<M>;
|
|
209
|
-
orderBy(column: keyof M & string, direction?: "asc" | "desc"): QueryBuilderInterface<M>;
|
|
288
|
+
orderBy(column: (keyof M & string) | ComputedSortField, direction?: "asc" | "desc"): QueryBuilderInterface<M>;
|
|
210
289
|
limit(count: number): QueryBuilderInterface<M>;
|
|
211
290
|
offset(count: number): QueryBuilderInterface<M>;
|
|
212
|
-
search(searchString: string): QueryBuilderInterface<M>;
|
|
291
|
+
search(searchString: string, options?: { explain?: boolean }): QueryBuilderInterface<M>;
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Order rows by nearest-neighbour distance to `vector`, closest first.
|
|
295
|
+
*
|
|
296
|
+
* Postgres only, over a property declared as `type: "vector"`. Each row
|
|
297
|
+
* comes back with a `_distance`. Any `where` on the same query filters
|
|
298
|
+
* before the ordering; distance decides the order.
|
|
299
|
+
*
|
|
300
|
+
* The query embedding is the caller's to produce.
|
|
301
|
+
*/
|
|
302
|
+
vectorSearch(
|
|
303
|
+
property: string,
|
|
304
|
+
vector: number[],
|
|
305
|
+
options?: { distance?: "cosine" | "l2" | "inner_product"; threshold?: number }
|
|
306
|
+
): QueryBuilderInterface<M>;
|
|
213
307
|
include(...relations: string[]): QueryBuilderInterface<M>;
|
|
214
308
|
}
|
|
215
309
|
|
|
@@ -240,12 +334,55 @@ export interface PaginationMeta {
|
|
|
240
334
|
* @group Data
|
|
241
335
|
*/
|
|
242
336
|
export interface FindResult<M extends Record<string, unknown> = Record<string, unknown>> {
|
|
243
|
-
/**
|
|
244
|
-
|
|
337
|
+
/**
|
|
338
|
+
* Flat rows matching the query, each carrying whatever the query computed
|
|
339
|
+
* for it — see {@link QueryComputedFields}.
|
|
340
|
+
*/
|
|
341
|
+
data: (M & QueryComputedFields)[];
|
|
245
342
|
/** Pagination metadata */
|
|
246
343
|
meta: PaginationMeta;
|
|
247
344
|
}
|
|
248
345
|
|
|
346
|
+
/**
|
|
347
|
+
* Values a query attaches to a row that are not columns of it.
|
|
348
|
+
*
|
|
349
|
+
* Both are absent unless the query asked for the thing that produces them, so
|
|
350
|
+
* both are optional — and reading one on a query that did not ask returns
|
|
351
|
+
* `undefined` rather than a wrong number.
|
|
352
|
+
*
|
|
353
|
+
* They live here rather than on the row type because a generated row type
|
|
354
|
+
* describes a *table*, and neither of these is in one. Without this, a caller
|
|
355
|
+
* who sorted by relevance could not then read the relevance.
|
|
356
|
+
*
|
|
357
|
+
* A `type` alias, deliberately, not an `interface`. TypeScript grants an
|
|
358
|
+
* implicit index signature to a type alias and withholds it from an interface,
|
|
359
|
+
* so `Row & QueryComputedFields` stops being assignable to
|
|
360
|
+
* `Record<string, unknown>` the moment this becomes an interface. Seven casts
|
|
361
|
+
* in one downstream app broke on exactly that.
|
|
362
|
+
*
|
|
363
|
+
* @group Data
|
|
364
|
+
*/
|
|
365
|
+
export type QueryComputedFields = {
|
|
366
|
+
/**
|
|
367
|
+
* Relevance, when the collection declares a {@link SearchConfig} and the
|
|
368
|
+
* query carried a search string. Higher is better; the scale is not
|
|
369
|
+
* comparable between two different search strings.
|
|
370
|
+
*/
|
|
371
|
+
_score?: number;
|
|
372
|
+
/**
|
|
373
|
+
* Which declared fields matched, and the text around each hit. Present only
|
|
374
|
+
* when the query asked for it — `.search(term, { explain: true })` — because
|
|
375
|
+
* it costs a `ts_headline` per field per row.
|
|
376
|
+
*/
|
|
377
|
+
_matches?: SearchMatch[];
|
|
378
|
+
/**
|
|
379
|
+
* Distance to the query vector, when the query used
|
|
380
|
+
* {@link FindParams.vectorSearch}. Lower is closer, and the rows are
|
|
381
|
+
* already ordered by it.
|
|
382
|
+
*/
|
|
383
|
+
_distance?: number;
|
|
384
|
+
};
|
|
385
|
+
|
|
249
386
|
/**
|
|
250
387
|
* Which column an iteration seeks on, for keyset ("seek") pagination.
|
|
251
388
|
*
|
|
@@ -340,10 +477,25 @@ export type FindAllParams<M extends Record<string, unknown> = Record<string, unk
|
|
|
340
477
|
export interface SDKQueryBuilderInterface<M extends Record<string, unknown> = Record<string, unknown>> {
|
|
341
478
|
where<K extends keyof M & string>(column: K, operator: WhereFilterOp, value: WhereValue<M[K]>): this;
|
|
342
479
|
where(logicalCondition: LogicalCondition): this;
|
|
343
|
-
orderBy(column: keyof M & string, direction?: "asc" | "desc"): this;
|
|
480
|
+
orderBy(column: (keyof M & string) | ComputedSortField, direction?: "asc" | "desc"): this;
|
|
344
481
|
limit(count: number): this;
|
|
345
482
|
offset(count: number): this;
|
|
346
|
-
search(searchString: string): this;
|
|
483
|
+
search(searchString: string, options?: { explain?: boolean }): this;
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* Order rows by nearest-neighbour distance to `vector`, closest first.
|
|
487
|
+
*
|
|
488
|
+
* Postgres only, over a property declared as `type: "vector"`. Each row
|
|
489
|
+
* comes back with a `_distance`. Any `where` on the same query filters
|
|
490
|
+
* before the ordering; distance decides the order.
|
|
491
|
+
*
|
|
492
|
+
* The query embedding is the caller's to produce.
|
|
493
|
+
*/
|
|
494
|
+
vectorSearch(
|
|
495
|
+
property: string,
|
|
496
|
+
vector: number[],
|
|
497
|
+
options?: { distance?: "cosine" | "l2" | "inner_product"; threshold?: number }
|
|
498
|
+
): this;
|
|
347
499
|
include(...relations: string[]): this;
|
|
348
500
|
find(): Promise<FindResult<M>>;
|
|
349
501
|
count(): Promise<number>;
|
|
@@ -501,6 +653,12 @@ export interface SDKCollectionClient<
|
|
|
501
653
|
* Batches are capped server-side (1000 rows by default) because one batch
|
|
502
654
|
* holds its locks for the whole transaction — chunk larger jobs.
|
|
503
655
|
*
|
|
656
|
+
* Pass {@link WriteOptions.idempotencyKey} on anything that may be retried.
|
|
657
|
+
* A client that never sees the response cannot know whether the batch
|
|
658
|
+
* committed, and without a key the server cannot tell the retry from a
|
|
659
|
+
* second genuine import — so it performs it again, duplicating every row in
|
|
660
|
+
* the batch rather than just one.
|
|
661
|
+
*
|
|
504
662
|
* @returns The written rows, in the order given.
|
|
505
663
|
*
|
|
506
664
|
* @example
|
|
@@ -510,7 +668,7 @@ export interface SDKCollectionClient<
|
|
|
510
668
|
* }
|
|
511
669
|
* ```
|
|
512
670
|
*/
|
|
513
|
-
createMany(data: I[], options?: { upsert?: boolean }): Promise<M[]>;
|
|
671
|
+
createMany(data: I[], options?: { upsert?: boolean } & WriteOptions): Promise<M[]>;
|
|
514
672
|
|
|
515
673
|
/**
|
|
516
674
|
* Update an existing record by ID.
|
|
@@ -520,6 +678,46 @@ export interface SDKCollectionClient<
|
|
|
520
678
|
*/
|
|
521
679
|
update(id: string | number, data: U): Promise<M>;
|
|
522
680
|
|
|
681
|
+
/**
|
|
682
|
+
* Update many records in a single request and a single transaction.
|
|
683
|
+
*
|
|
684
|
+
* The counterpart to {@link createMany}, and the reason it exists is the
|
|
685
|
+
* same: one call per row means one HTTP round trip and one transaction per
|
|
686
|
+
* row. Every record still runs the normal pipeline — callbacks, relations,
|
|
687
|
+
* row-level security — and the batch is all-or-nothing, so a rejected
|
|
688
|
+
* record leaves none of them written and the error names the offending
|
|
689
|
+
* index.
|
|
690
|
+
*
|
|
691
|
+
* Each entry is `{ id, data }` rather than a flat row carrying its own key.
|
|
692
|
+
* That is deliberate: on a table keyed on something other than `id` — a
|
|
693
|
+
* `sku`, a composite key — a flat row cannot say whether a column is the
|
|
694
|
+
* address or a value to write. Naming the address separately mirrors
|
|
695
|
+
* single-row `update(id, data)` exactly and leaves nothing to infer.
|
|
696
|
+
*
|
|
697
|
+
* An id that matches no row fails the batch with a 404 rather than being
|
|
698
|
+
* skipped, for the same reason `update()` does: silently updating four of
|
|
699
|
+
* five rows is worse than updating none.
|
|
700
|
+
*
|
|
701
|
+
* Batches share `createMany`'s server-side cap (1000 rows by default),
|
|
702
|
+
* because one batch holds its locks for the whole transaction.
|
|
703
|
+
*
|
|
704
|
+
* Pass {@link WriteOptions.idempotencyKey} on anything that may be retried.
|
|
705
|
+
* An update replayed in full is naturally idempotent, but one interleaved
|
|
706
|
+
* with another writer's is not — the key is what stops a lost ACK from
|
|
707
|
+
* re-applying a stale batch over newer data.
|
|
708
|
+
*
|
|
709
|
+
* @returns The updated rows, in the order given.
|
|
710
|
+
*
|
|
711
|
+
* @example
|
|
712
|
+
* ```ts
|
|
713
|
+
* await client.data.orders.updateMany([
|
|
714
|
+
* { id: "o-1", data: { status: "shipped" } },
|
|
715
|
+
* { id: "o-2", data: { status: "shipped" } }
|
|
716
|
+
* ]);
|
|
717
|
+
* ```
|
|
718
|
+
*/
|
|
719
|
+
updateMany(updates: { id: string | number; data: U }[], options?: WriteOptions): Promise<M[]>;
|
|
720
|
+
|
|
523
721
|
/**
|
|
524
722
|
* Delete a record by ID.
|
|
525
723
|
* @throws {RebaseApiError} with status 404 when the record does not exist.
|
|
@@ -527,13 +725,48 @@ export interface SDKCollectionClient<
|
|
|
527
725
|
delete(id: string | number): Promise<void>;
|
|
528
726
|
|
|
529
727
|
/**
|
|
530
|
-
*
|
|
728
|
+
* Delete many records in a single request and a single transaction.
|
|
729
|
+
*
|
|
730
|
+
* Takes ids, not a filter. A filter-shaped bulk delete is a different and
|
|
731
|
+
* far more dangerous operation — the failure mode is an omitted or
|
|
732
|
+
* mistyped condition emptying a table, and it cannot be reviewed at the
|
|
733
|
+
* call site the way an explicit list can. Read first, then pass the ids you
|
|
734
|
+
* meant.
|
|
735
|
+
*
|
|
736
|
+
* `beforeDelete` and `afterDelete` fire per row, exactly as they do for
|
|
737
|
+
* single deletes, and returning `false` from `beforeDelete` fails the batch
|
|
738
|
+
* rather than quietly dropping one row from it. All-or-nothing, so an id
|
|
739
|
+
* that matches no row 404s the whole call.
|
|
740
|
+
*
|
|
741
|
+
* Shares `createMany`'s row cap.
|
|
742
|
+
*
|
|
743
|
+
* @example
|
|
744
|
+
* ```ts
|
|
745
|
+
* const stale = await client.data.sessions.findAll({
|
|
746
|
+
* where: { expires_at: ["<", cutoff] }
|
|
747
|
+
* });
|
|
748
|
+
* await client.data.sessions.deleteMany(stale.map(s => s.id as string));
|
|
749
|
+
* ```
|
|
531
750
|
*/
|
|
532
|
-
|
|
751
|
+
deleteMany(ids: (string | number)[], options?: WriteOptions): Promise<void>;
|
|
533
752
|
|
|
534
753
|
/**
|
|
535
|
-
*
|
|
754
|
+
* The low-level realtime subscription: raw server pushes, nothing else.
|
|
755
|
+
*
|
|
756
|
+
* **Prefer `observe()`** on a client from `@rebasepro/client`, which wraps
|
|
757
|
+
* this one and is what a UI actually wants — it emits from the local
|
|
758
|
+
* database first when offline is enabled, re-emits on local writes and
|
|
759
|
+
* rollbacks, and de-duplicates emissions so a refresh that changes nothing
|
|
760
|
+
* does not call back. `listen` does none of that; it forwards what the
|
|
761
|
+
* socket sends.
|
|
762
|
+
*
|
|
763
|
+
* Optional because it is only present when realtime is enabled. `observe()`
|
|
764
|
+
* is not — it degrades to a single fetch — which is the other reason to
|
|
765
|
+
* reach for it instead.
|
|
536
766
|
*/
|
|
767
|
+
listen?(params: FindParams<M> | undefined, onUpdate: (response: FindResult<M>) => void, onError?: (error: Error) => void): () => void;
|
|
768
|
+
|
|
769
|
+
/** {@link listen} for a single row. Prefer `observeById()`. */
|
|
537
770
|
listenById?(id: string | number, onUpdate: (row: M | undefined) => void, onError?: (error: Error) => void): () => void;
|
|
538
771
|
|
|
539
772
|
/**
|
|
@@ -544,15 +777,25 @@ export interface SDKCollectionClient<
|
|
|
544
777
|
// Fluent Query Builder
|
|
545
778
|
where<K extends keyof M & string>(column: K, operator: WhereFilterOp, value: WhereValue<M[K]>): SDKQueryBuilderInterface<M>;
|
|
546
779
|
where(logicalCondition: LogicalCondition): SDKQueryBuilderInterface<M>;
|
|
547
|
-
orderBy(column: keyof M & string, direction?: "asc" | "desc"): SDKQueryBuilderInterface<M>;
|
|
780
|
+
orderBy(column: (keyof M & string) | ComputedSortField, direction?: "asc" | "desc"): SDKQueryBuilderInterface<M>;
|
|
548
781
|
limit(count: number): SDKQueryBuilderInterface<M>;
|
|
549
782
|
offset(count: number): SDKQueryBuilderInterface<M>;
|
|
550
|
-
search(searchString: string): SDKQueryBuilderInterface<M>;
|
|
783
|
+
search(searchString: string, options?: { explain?: boolean }): SDKQueryBuilderInterface<M>;
|
|
784
|
+
/**
|
|
785
|
+
* Order rows by nearest-neighbour distance to `vector`, closest first.
|
|
786
|
+
* Postgres only, over a `type: "vector"` property. See
|
|
787
|
+
* {@link SDKQueryBuilderInterface.vectorSearch}.
|
|
788
|
+
*/
|
|
789
|
+
vectorSearch(
|
|
790
|
+
property: string,
|
|
791
|
+
vector: number[],
|
|
792
|
+
options?: { distance?: "cosine" | "l2" | "inner_product"; threshold?: number }
|
|
793
|
+
): SDKQueryBuilderInterface<M>;
|
|
551
794
|
include(...relations: string[]): SDKQueryBuilderInterface<M>;
|
|
552
795
|
}
|
|
553
796
|
|
|
554
797
|
/**
|
|
555
|
-
* The unified data access object for the **admin
|
|
798
|
+
* The unified data access object for the **admin panel** (Entity-shaped).
|
|
556
799
|
*
|
|
557
800
|
* Access collections as dynamic properties: `data.products.find(...)`. Each
|
|
558
801
|
* accessor returns `Entity`-wrapped records (`{ id, path, values }`) — the
|
|
@@ -561,7 +804,7 @@ export interface SDKCollectionClient<
|
|
|
561
804
|
*
|
|
562
805
|
* @internal App developers do **not** use this — they use
|
|
563
806
|
* {@link RebaseSdkData} (flat rows), which is what the SDK client and backend
|
|
564
|
-
* `context.data` expose. This Entity-shaped map backs the admin
|
|
807
|
+
* `context.data` expose. This Entity-shaped map backs the admin panel only.
|
|
565
808
|
*
|
|
566
809
|
* @group Data
|
|
567
810
|
*/
|
|
@@ -584,10 +827,15 @@ export type RebaseData<DB = unknown> = {
|
|
|
584
827
|
* Dynamic collection accessor.
|
|
585
828
|
* Access any collection by its slug as a property.
|
|
586
829
|
*
|
|
830
|
+
* The index signature is `CollectionAccessor` alone, for the reason
|
|
831
|
+
* spelled out on {@link RebaseSdkData}: unioning in the `collection`
|
|
832
|
+
* method's own signature is unnecessary across an intersection, and it
|
|
833
|
+
* costs `data.products.find()` — the access this `@example` documents.
|
|
834
|
+
*
|
|
587
835
|
* @example
|
|
588
836
|
* data.products.find({ where: { status: ["==", "published"] } })
|
|
589
837
|
*/
|
|
590
|
-
[collectionSlug: string]: CollectionAccessor
|
|
838
|
+
[collectionSlug: string]: CollectionAccessor;
|
|
591
839
|
}
|
|
592
840
|
);
|
|
593
841
|
|
|
@@ -639,6 +887,26 @@ export type InsertOf<T> = T extends { Insert: infer I extends Record<string, unk
|
|
|
639
887
|
*/
|
|
640
888
|
export type UpdateOf<T> = T extends { Update: infer U extends Record<string, unknown> } ? U : Partial<RowOf<T>>;
|
|
641
889
|
|
|
890
|
+
/**
|
|
891
|
+
* Note on the untyped branch below: its index signature is
|
|
892
|
+
* `SDKCollectionClient`, NOT `SDKCollectionClient | ((slug: string) => …)`.
|
|
893
|
+
*
|
|
894
|
+
* The union looks like it is needed so `collection` — a method on this same
|
|
895
|
+
* object — satisfies the index signature. It is not, because `collection` is
|
|
896
|
+
* declared in a *separate* member of the intersection, and TypeScript only
|
|
897
|
+
* requires named properties to be assignable to an index signature declared
|
|
898
|
+
* alongside them. Including the function arm cost the documented accessor:
|
|
899
|
+
*
|
|
900
|
+
* rebase.dataAsAdmin.projects.find()
|
|
901
|
+
* // ^ Property 'find' does not exist on type
|
|
902
|
+
* // 'SDKCollectionClient | ((slug: string) => …)'
|
|
903
|
+
*
|
|
904
|
+
* Every project without a generated `Database` type lands on this branch, so
|
|
905
|
+
* property-style access — the form used by the `@example` below, by the
|
|
906
|
+
* scaffolded function template, and by the 0.13 migration note — did not
|
|
907
|
+
* compile for any of them. Do not restore the arm; use `collection(slug)` if a
|
|
908
|
+
* caller genuinely needs the by-slug function.
|
|
909
|
+
*/
|
|
642
910
|
export type RebaseSdkData<DB = unknown> = {
|
|
643
911
|
/**
|
|
644
912
|
* Get a flat collection accessor by slug.
|
|
@@ -659,6 +927,6 @@ export type RebaseSdkData<DB = unknown> = {
|
|
|
659
927
|
* @example
|
|
660
928
|
* data.products.find({ where: { status: ["==", "published"] } })
|
|
661
929
|
*/
|
|
662
|
-
[collectionSlug: string]: SDKCollectionClient
|
|
930
|
+
[collectionSlug: string]: SDKCollectionClient;
|
|
663
931
|
}
|
|
664
932
|
);
|
|
@@ -117,6 +117,8 @@ export interface FetchCollectionProps<M extends Record<string, unknown> = Record
|
|
|
117
117
|
startAfter?: unknown;
|
|
118
118
|
orderBy?: string;
|
|
119
119
|
searchString?: string;
|
|
120
|
+
/** Ask each row which declared search field matched — populates `_matches`. */
|
|
121
|
+
searchExplain?: boolean;
|
|
120
122
|
order?: "desc" | "asc";
|
|
121
123
|
/** Vector similarity search configuration */
|
|
122
124
|
vectorSearch?: VectorSearchParams;
|
|
@@ -169,6 +171,23 @@ export interface SaveManyProps<M extends Record<string, unknown> = Record<string
|
|
|
169
171
|
upsert?: boolean;
|
|
170
172
|
}
|
|
171
173
|
|
|
174
|
+
/**
|
|
175
|
+
* @internal
|
|
176
|
+
*/
|
|
177
|
+
export interface UpdateManyProps<M extends Record<string, unknown> = Record<string, unknown>> {
|
|
178
|
+
path: string;
|
|
179
|
+
/**
|
|
180
|
+
* The rows to update, each named by its address.
|
|
181
|
+
*
|
|
182
|
+
* Distinct from {@link SaveManyProps.rows}, which carries keys *inside* the
|
|
183
|
+
* values and is insert-shaped — `saveMany` passes `status: "new"` and no
|
|
184
|
+
* `id`, so it cannot express "update exactly this row". This can, and it is
|
|
185
|
+
* why bulk update is a separate driver method rather than a flag on that one.
|
|
186
|
+
*/
|
|
187
|
+
updates: { id: string | number; values: Partial<EntityValues<M>> }[];
|
|
188
|
+
collection?: CollectionConfig<M>;
|
|
189
|
+
}
|
|
190
|
+
|
|
172
191
|
/**
|
|
173
192
|
* @internal
|
|
174
193
|
*/
|
|
@@ -177,6 +196,15 @@ export interface DeleteProps<M extends Record<string, unknown> = Record<string,
|
|
|
177
196
|
collection?: CollectionConfig<M>;
|
|
178
197
|
}
|
|
179
198
|
|
|
199
|
+
/**
|
|
200
|
+
* @internal
|
|
201
|
+
*/
|
|
202
|
+
export interface DeleteManyProps<M extends Record<string, unknown> = Record<string, unknown>> {
|
|
203
|
+
path: string;
|
|
204
|
+
ids: (string | number)[];
|
|
205
|
+
collection?: CollectionConfig<M>;
|
|
206
|
+
}
|
|
207
|
+
|
|
180
208
|
export type FilterCombinationValidProps = {
|
|
181
209
|
path: string;
|
|
182
210
|
databaseId?: string;
|
|
@@ -258,6 +286,17 @@ export interface DataDriver {
|
|
|
258
286
|
*/
|
|
259
287
|
saveMany?<M extends Record<string, unknown> = Record<string, unknown>>(props: SaveManyProps<M>): Promise<Record<string, unknown>[]>;
|
|
260
288
|
|
|
289
|
+
/**
|
|
290
|
+
* Update many rows in one transaction, each addressed by id.
|
|
291
|
+
*
|
|
292
|
+
* Optional for the same reason `saveMany` is: a driver that cannot make the
|
|
293
|
+
* batch atomic should not pretend to. The REST layer reports
|
|
294
|
+
* `BULK_UNSUPPORTED` rather than silently falling back to a loop of single
|
|
295
|
+
* writes, which would be neither atomic nor one round trip — the two things
|
|
296
|
+
* a caller reaches for a batch to get.
|
|
297
|
+
*/
|
|
298
|
+
updateMany?<M extends Record<string, unknown> = Record<string, unknown>>(props: UpdateManyProps<M>): Promise<Record<string, unknown>[]>;
|
|
299
|
+
|
|
261
300
|
/**
|
|
262
301
|
* Delete a entity
|
|
263
302
|
* @param props
|
|
@@ -271,6 +310,14 @@ export interface DataDriver {
|
|
|
271
310
|
*/
|
|
272
311
|
deleteAll?(path: string): Promise<void>;
|
|
273
312
|
|
|
313
|
+
/**
|
|
314
|
+
* Delete many rows in one transaction, addressed by id.
|
|
315
|
+
*
|
|
316
|
+
* Ids rather than a filter, deliberately — see
|
|
317
|
+
* {@link SDKCollectionClient.deleteMany}. Optional, as `saveMany` is.
|
|
318
|
+
*/
|
|
319
|
+
deleteMany?<M extends Record<string, unknown> = Record<string, unknown>>(props: DeleteManyProps<M>): Promise<void>;
|
|
320
|
+
|
|
274
321
|
/**
|
|
275
322
|
* Check if the given property is unique in the given collection
|
|
276
323
|
* @param path Collection path
|
|
@@ -383,6 +430,8 @@ export interface RestFetchService {
|
|
|
383
430
|
offset?: number;
|
|
384
431
|
startAfter?: Record<string, unknown>;
|
|
385
432
|
searchString?: string;
|
|
433
|
+
/** Ask each row which declared search fields matched — populates `_matches`. */
|
|
434
|
+
searchExplain?: boolean;
|
|
386
435
|
databaseId?: string;
|
|
387
436
|
vectorSearch?: VectorSearchParams;
|
|
388
437
|
},
|
package/src/errors.ts
CHANGED
|
@@ -1,3 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The error codes every route can produce, as `RebaseApiError.code`.
|
|
3
|
+
*
|
|
4
|
+
* These are the defaults on `ApiError`'s static constructors server-side, so
|
|
5
|
+
* any endpoint can answer with one. They are **not** the complete set: routes
|
|
6
|
+
* pass their own more specific codes too (`EMAIL_EXISTS`, `TOKEN_EXPIRED`,
|
|
7
|
+
* `INVALID_BULK_BODY`, …), and auth alone defines a couple of dozen.
|
|
8
|
+
*
|
|
9
|
+
* Hence the union is deliberately open rather than closed. It exists to give
|
|
10
|
+
* autocomplete and to catch a typo in the common cases — `code` was a bare
|
|
11
|
+
* `string`, so `e.code === "NOT_FOUND"` and `e.code === "NOTFOUND"` were
|
|
12
|
+
* equally valid and only one of them worked. Closing it would be a lie that
|
|
13
|
+
* broke the moment a route added a code.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* if (e instanceof RebaseApiError) {
|
|
17
|
+
* switch (e.code) {
|
|
18
|
+
* case "NOT_FOUND": return null; // completed
|
|
19
|
+
* case "FORBIDDEN": return redirect();
|
|
20
|
+
* default: throw e; // routes' own codes land here
|
|
21
|
+
* }
|
|
22
|
+
* }
|
|
23
|
+
*
|
|
24
|
+
* @group Errors
|
|
25
|
+
*/
|
|
26
|
+
export type RebaseErrorCode =
|
|
27
|
+
| "BAD_REQUEST"
|
|
28
|
+
| "UNAUTHORIZED"
|
|
29
|
+
| "FORBIDDEN"
|
|
30
|
+
| "NOT_FOUND"
|
|
31
|
+
| "CONFLICT"
|
|
32
|
+
| "INTERNAL_ERROR"
|
|
33
|
+
| "SERVICE_UNAVAILABLE"
|
|
34
|
+
| "DB_PERMISSION_DENIED"
|
|
35
|
+
| "SCHEMA_DRIFT"
|
|
36
|
+
// `string & {}` keeps the union open while preserving completion on the
|
|
37
|
+
// literals above — a bare `| string` would collapse them and offer nothing.
|
|
38
|
+
| (string & {});
|
|
39
|
+
|
|
1
40
|
/**
|
|
2
41
|
* Structured initializer for {@link RebaseApiError}.
|
|
3
42
|
*
|
|
@@ -10,8 +49,8 @@ export interface RebaseErrorInit {
|
|
|
10
49
|
* logic errors that have no HTTP status.
|
|
11
50
|
*/
|
|
12
51
|
status?: number;
|
|
13
|
-
/** Stable, machine-readable error code
|
|
14
|
-
code?:
|
|
52
|
+
/** Stable, machine-readable error code. See {@link RebaseErrorCode}. */
|
|
53
|
+
code?: RebaseErrorCode;
|
|
15
54
|
/** Structured error payload returned by the server, when present. */
|
|
16
55
|
details?: unknown;
|
|
17
56
|
/** The underlying error this one wraps, if any. */
|
|
@@ -45,8 +84,8 @@ export interface RebaseErrorInit {
|
|
|
45
84
|
export class RebaseApiError extends Error {
|
|
46
85
|
/** HTTP status code, or `undefined` for non-HTTP errors. */
|
|
47
86
|
readonly status?: number;
|
|
48
|
-
/** Stable machine-readable error code, when the server supplied one. */
|
|
49
|
-
readonly code?:
|
|
87
|
+
/** Stable machine-readable error code, when the server supplied one. See {@link RebaseErrorCode}. */
|
|
88
|
+
readonly code?: RebaseErrorCode;
|
|
50
89
|
/** Structured error payload from the server, when present. */
|
|
51
90
|
readonly details?: unknown;
|
|
52
91
|
|
package/src/types/admin_block.ts
CHANGED
|
@@ -41,6 +41,7 @@ export const ADMIN_COLLECTION_KEYS = [
|
|
|
41
41
|
"defaultSize",
|
|
42
42
|
"defaultViewMode",
|
|
43
43
|
"disableDefaultActions",
|
|
44
|
+
"display",
|
|
44
45
|
"enabledViews",
|
|
45
46
|
"entityActions",
|
|
46
47
|
"entityViews",
|
|
@@ -51,6 +52,7 @@ export const ADMIN_COLLECTION_KEYS = [
|
|
|
51
52
|
"formAutoSave",
|
|
52
53
|
"formView",
|
|
53
54
|
"group",
|
|
55
|
+
"hideFromEntityViews",
|
|
54
56
|
"hideFromNavigation",
|
|
55
57
|
"hideIdFromCollection",
|
|
56
58
|
"hideIdFromForm",
|