@rebasepro/types 0.13.1-canary.gef9608c → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/dist/call_context.d.ts +70 -4
  2. package/dist/controllers/client.d.ts +47 -74
  3. package/dist/controllers/data.d.ts +345 -33
  4. package/dist/controllers/data_driver.d.ts +106 -8
  5. package/dist/errors.d.ts +30 -4
  6. package/dist/index.es.js +217 -12
  7. package/dist/index.es.js.map +1 -1
  8. package/dist/types/admin_block.d.ts +38 -1
  9. package/dist/types/auth_adapter.d.ts +7 -0
  10. package/dist/types/backend.d.ts +2 -0
  11. package/dist/types/collections.d.ts +46 -8
  12. package/dist/types/cron.d.ts +50 -9
  13. package/dist/types/entities.d.ts +11 -0
  14. package/dist/types/entity_callbacks.d.ts +2 -1
  15. package/dist/types/index.d.ts +2 -0
  16. package/dist/types/policy.d.ts +13 -13
  17. package/dist/types/properties.d.ts +31 -6
  18. package/dist/types/rls-functions.d.ts +84 -0
  19. package/dist/types/search.d.ts +231 -0
  20. package/package.json +1 -1
  21. package/src/call_context.ts +68 -4
  22. package/src/controllers/client.ts +47 -95
  23. package/src/controllers/data.ts +356 -33
  24. package/src/controllers/data_driver.ts +144 -11
  25. package/src/errors.ts +43 -4
  26. package/src/types/admin_block.ts +84 -0
  27. package/src/types/auth_adapter.ts +7 -0
  28. package/src/types/backend.ts +2 -0
  29. package/src/types/collections.ts +47 -8
  30. package/src/types/cron.ts +51 -9
  31. package/src/types/entities.ts +12 -0
  32. package/src/types/entity_callbacks.ts +2 -1
  33. package/src/types/filter-operators.ts +17 -1
  34. package/src/types/index.ts +2 -0
  35. package/src/types/policy.ts +13 -13
  36. package/src/types/properties.ts +32 -6
  37. package/src/types/rls-functions.ts +98 -0
  38. package/src/types/search.ts +247 -0
@@ -1,6 +1,67 @@
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";
5
+ /**
6
+ * Operator-blind filter value: whatever the column holds, a list of it, or null.
7
+ *
8
+ * @deprecated Superseded by {@link WhereValueFor}, which correlates the value
9
+ * with the operator. Kept exported because it is public API and downstream code
10
+ * annotates with it; every `where()` overload in this file uses `WhereValueFor`.
11
+ */
3
12
  export type WhereValue<T> = T | T[] | null;
13
+ /**
14
+ * The element type of an array column, and the column's own type otherwise.
15
+ *
16
+ * A generated SDK emits an `array` property as `Array<X>` and a to-many
17
+ * relation as `Array<TargetRow>`, so this is what `array-contains` compares
18
+ * against on either.
19
+ */
20
+ export type ElementOf<T> = T extends readonly (infer E)[] ? E : T;
21
+ /**
22
+ * The `id` of a row-shaped element, and `never` for anything else.
23
+ *
24
+ * A to-many relation is emitted as `Array<TargetRow>`, but the filter compilers
25
+ * compare a relation by **id** — `buildRelationFilterPredicate` in
26
+ * `@rebasepro/server-postgres` unwraps a relation value down to its id — so
27
+ * `where("tags", "array-contains", tagId)` is the call that works, and the
28
+ * element type alone would refuse it.
29
+ */
30
+ export type IdOf<E> = E extends {
31
+ id: infer I;
32
+ } ? I : never;
33
+ /**
34
+ * One member of an array column: its element, or — when the element is a row —
35
+ * that row's id, which is what a relation filter is actually compared against.
36
+ */
37
+ export type WhereElementOf<T> = ElementOf<T> | IdOf<ElementOf<T>>;
38
+ /**
39
+ * The value a given operator takes on a column of type `T`.
40
+ *
41
+ * `WhereValue<T>` was one value type for all sixteen operators, which made
42
+ * `array-contains` uncallable from a generated SDK — it is the one operator
43
+ * whose value is an *element* of the column rather than the column's own type,
44
+ * so on `tags: string[]` it wanted a `string[]` and the documented
45
+ * `.where("tags", "array-contains", "featured")` was a compile error. The
46
+ * spelling that did compile, `["featured"]`, builds `@> ARRAY[$1]` with the
47
+ * whole array bound as the single element and matches nothing: the correct
48
+ * query rejected, the accepted query silently wrong.
49
+ *
50
+ * The branches mirror `buildSingleFilterCondition` in `@rebasepro/server-postgres`:
51
+ *
52
+ * - `array-contains` → one element of the column (or a related row's id).
53
+ * - `in` / `not-in` / `array-contains-any` → a list of elements; a bare element
54
+ * is read as the one-element list, and `null` is a null check.
55
+ * - `like` / `ilike` / `not-like` / `not-ilike` → a SQL pattern. Always a
56
+ * string, including on numeric and date columns, which the driver casts.
57
+ * - `is-null` / `is-not-null` → nothing; the value is ignored everywhere.
58
+ * - everything else → the column's own type, or `null` for a null comparison.
59
+ *
60
+ * Distributes over `Op`, so a caller holding an unnarrowed `WhereFilterOp`
61
+ * (a dynamic filter UI, say) gets the union of every branch and stays as
62
+ * permissive as it was.
63
+ */
64
+ export type WhereValueFor<Op extends WhereFilterOp, T> = Op extends "array-contains" ? WhereElementOf<T> : Op extends "in" | "not-in" | "array-contains-any" ? readonly WhereElementOf<T>[] | WhereElementOf<T> | null : Op extends "like" | "ilike" | "not-like" | "not-ilike" ? string : Op extends "is-null" | "is-not-null" ? null | undefined : T | null;
4
65
  export interface LogicalCondition {
5
66
  type: "and" | "or";
6
67
  conditions: (FilterCondition | LogicalCondition)[];
@@ -43,9 +104,14 @@ export interface FindParams<M extends Record<string, unknown> = Record<string, u
43
104
  /**
44
105
  * Maximum number of items to return.
45
106
  *
46
- * Defaults to {@link DEFAULT_LIST_LIMIT}, and is clamped to
47
- * {@link MAX_LIST_LIMIT}. Both bounds are applied by the backend, so a
48
- * read is never unbounded whether or not a limit was asked for.
107
+ * Omit it and the backend applies {@link DEFAULT_LIST_LIMIT}, so a read is
108
+ * never unbounded. Provide it and it must be a whole number between 1 and
109
+ * {@link MAX_LIST_LIMIT}: the backend **rejects** anything else with a 400
110
+ * rather than trimming it to fit, because a page quietly smaller than the
111
+ * one you asked for is indistinguishable from having reached the end of the
112
+ * collection. To read past the ceiling, page with `offset` — or let
113
+ * {@link SDKCollectionClient.iterate} / {@link SDKCollectionClient.findAll}
114
+ * do it for you.
49
115
  */
50
116
  limit?: number;
51
117
  /**
@@ -82,7 +148,7 @@ export interface FindParams<M extends Record<string, unknown> = Record<string, u
82
148
  * Sort order as a `[field, direction]` tuple.
83
149
  * @example orderBy: ["created_at", "desc"]
84
150
  */
85
- orderBy?: OrderByTuple<FieldPath<M>>;
151
+ orderBy?: OrderByTuple<FieldPath<M> | ComputedSortField>;
86
152
  /**
87
153
  * Relations to include in the response.
88
154
  *
@@ -92,11 +158,45 @@ export interface FindParams<M extends Record<string, unknown> = Record<string, u
92
158
  */
93
159
  include?: string[];
94
160
  /**
95
- * Full-text search string. Matched (OR-ed) across the collection's
96
- * searchable columns, then AND-ed with `where`/`logical`. This is the
97
- * value behind the query builder's `.search()` method.
161
+ * Text search string, AND-ed with `where`/`logical`. This is the value
162
+ * behind the query builder's `.search()` method.
163
+ *
164
+ * What it compiles to depends on the collection. By default — matching
165
+ * every collection that has not said otherwise — it is a case-insensitive
166
+ * substring match OR-ed across the collection's top-level `string`
167
+ * properties: it does not reach inside `map` or `array` properties, it does
168
+ * not stem or rank, and it cannot use an index.
169
+ *
170
+ * A Postgres collection that declares a `search` block instead gets a
171
+ * ranked full-text match over exactly the fields it named, and rows come
172
+ * back with a {@link FindParams.orderBy}-able `_score`.
98
173
  */
99
174
  searchString?: string;
175
+ /**
176
+ * Nearest-neighbour search over a `vector` property.
177
+ *
178
+ * Postgres only, and only for a collection that declares a property of
179
+ * type `vector`. Rows come back ordered by distance, closest first, each
180
+ * carrying a `_distance`. Combines with `where` and `logical`, which are
181
+ * applied as filters before the ordering — so this is "the nearest rows
182
+ * that also match", not "the nearest rows, then filtered".
183
+ *
184
+ * Supplying the query vector is the caller's job: rebase stores and
185
+ * searches embeddings, it does not compute them.
186
+ */
187
+ vectorSearch?: VectorSearchParams;
188
+ /**
189
+ * Ask each returned row to explain itself: which declared search fields
190
+ * matched, with a highlighted snippet from each. Populates `_matches`.
191
+ *
192
+ * Off by default because it is not free — one `ts_headline` per declared
193
+ * field per returned row, and `ts_headline` re-parses the document rather
194
+ * than reading the index. Fine for a page of results, not for an export.
195
+ *
196
+ * Ignored unless the collection declares a `search` block and the query
197
+ * carries a `searchString`; there is nothing to explain otherwise.
198
+ */
199
+ searchExplain?: boolean;
100
200
  }
101
201
  /**
102
202
  * Paginated response from a collection query.
@@ -114,34 +214,49 @@ export interface FindResponse<M extends Record<string, unknown> = Record<string,
114
214
  };
115
215
  }
116
216
  /**
117
- * Fluent query builder for the **admin admin** — resolves to `FindResponse<M>`
217
+ * Fluent query builder for the **admin panel** — resolves to `FindResponse<M>`
118
218
  * (Snapshot-wrapped rows).
119
219
  *
120
220
  * @internal App developers should use {@link SDKQueryBuilderInterface}
121
221
  * (flat rows, returned by `client.data.*` / `context.data.*`). This
122
- * Snapshot-flavored variant backs the admin admin internals only.
222
+ * Snapshot-flavored variant backs the admin panel internals only.
123
223
  *
124
224
  * @group Data
125
225
  */
126
226
  export interface QueryBuilderInterface<M extends Record<string, unknown> = Record<string, unknown>> {
127
- where<K extends keyof M & string>(column: K, operator: WhereFilterOp, value: WhereValue<M[K]>): this;
227
+ where<K extends keyof M & string, Op extends WhereFilterOp>(column: K, operator: Op, value: WhereValueFor<Op, M[K]>): this;
128
228
  where(logicalCondition: LogicalCondition): this;
129
- orderBy(column: keyof M & string, direction?: "asc" | "desc"): this;
229
+ orderBy(column: (keyof M & string) | ComputedSortField, direction?: "asc" | "desc"): this;
130
230
  limit(count: number): this;
131
231
  offset(count: number): this;
132
- search(searchString: string): this;
232
+ search(searchString: string, options?: {
233
+ explain?: boolean;
234
+ }): this;
235
+ /**
236
+ * Order rows by nearest-neighbour distance to `vector`, closest first.
237
+ *
238
+ * Postgres only, over a property declared as `type: "vector"`. Each row
239
+ * comes back with a `_distance`. Any `where` on the same query filters
240
+ * before the ordering; distance decides the order.
241
+ *
242
+ * The query embedding is the caller's to produce.
243
+ */
244
+ vectorSearch(property: string, vector: number[], options?: {
245
+ distance?: "cosine" | "l2" | "inner_product";
246
+ threshold?: number;
247
+ }): this;
133
248
  include(...relations: string[]): this;
134
249
  find(): Promise<FindResponse<M>>;
135
250
  listen(onUpdate: (data: FindResponse<M>) => void, onError?: (error: Error) => void): () => void;
136
251
  }
137
252
  /**
138
- * A single collection's CRUD accessor for the **admin admin** — every method
253
+ * A single collection's CRUD accessor for the **admin panel** — every method
139
254
  * resolves to `Snapshot`-wrapped rows (`FindResponse<M>` / `Snapshot<M>`).
140
255
  *
141
256
  * @internal App developers do **not** use this. The public, symmetric surface
142
257
  * is {@link SDKCollectionClient} (flat rows), exposed as `client.data.products`
143
258
  * in the SDK and `context.data.products` in framework callbacks. This
144
- * Snapshot-flavored accessor backs the admin admin view-model only.
259
+ * Snapshot-flavored accessor backs the admin panel view-model only.
145
260
  *
146
261
  * @group Data
147
262
  */
@@ -175,6 +290,21 @@ export interface CollectionAccessor<M extends Record<string, unknown> = Record<s
175
290
  * @returns The updated entity
176
291
  */
177
292
  update(id: string | number, data: Partial<EntityValues<M>>): Promise<Entity<M>>;
293
+ /**
294
+ * Update many records in a single transaction.
295
+ *
296
+ * See {@link SDKCollectionClient.updateMany}. Optional, as `createMany` is.
297
+ */
298
+ updateMany?(updates: {
299
+ id: string | number;
300
+ data: Partial<EntityValues<M>>;
301
+ }[]): Promise<Entity<M>[]>;
302
+ /**
303
+ * Delete many records in a single transaction.
304
+ *
305
+ * See {@link SDKCollectionClient.deleteMany}. Optional, as `createMany` is.
306
+ */
307
+ deleteMany?(ids: (string | number)[]): Promise<void>;
178
308
  /**
179
309
  * Delete a record by ID.
180
310
  */
@@ -191,14 +321,35 @@ export interface CollectionAccessor<M extends Record<string, unknown> = Record<s
191
321
  listenById?(id: string | number, onUpdate: (entity: Entity<M> | undefined) => void, onError?: (error: Error) => void): () => void;
192
322
  /**
193
323
  * Count the number of records matching the given filter.
324
+ *
325
+ * Optional on this contract because a data source need not support it, and
326
+ * required on `CollectionClient` — the HTTP implementation always has it.
327
+ * So `client.data.posts.count()` compiles in the browser while the same
328
+ * call through a `context.data` accessor needs `count?.()`, which is the
329
+ * one place the two halves of this API are not interchangeable.
194
330
  */
195
331
  count?(params?: FindParams<M>): Promise<number>;
196
- where<K extends keyof M & string>(column: K, operator: WhereFilterOp, value: WhereValue<M[K]>): QueryBuilderInterface<M>;
332
+ where<K extends keyof M & string, Op extends WhereFilterOp>(column: K, operator: Op, value: WhereValueFor<Op, M[K]>): QueryBuilderInterface<M>;
197
333
  where(logicalCondition: LogicalCondition): QueryBuilderInterface<M>;
198
- orderBy(column: keyof M & string, direction?: "asc" | "desc"): QueryBuilderInterface<M>;
334
+ orderBy(column: (keyof M & string) | ComputedSortField, direction?: "asc" | "desc"): QueryBuilderInterface<M>;
199
335
  limit(count: number): QueryBuilderInterface<M>;
200
336
  offset(count: number): QueryBuilderInterface<M>;
201
- search(searchString: string): QueryBuilderInterface<M>;
337
+ search(searchString: string, options?: {
338
+ explain?: boolean;
339
+ }): QueryBuilderInterface<M>;
340
+ /**
341
+ * Order rows by nearest-neighbour distance to `vector`, closest first.
342
+ *
343
+ * Postgres only, over a property declared as `type: "vector"`. Each row
344
+ * comes back with a `_distance`. Any `where` on the same query filters
345
+ * before the ordering; distance decides the order.
346
+ *
347
+ * The query embedding is the caller's to produce.
348
+ */
349
+ vectorSearch(property: string, vector: number[], options?: {
350
+ distance?: "cosine" | "l2" | "inner_product";
351
+ threshold?: number;
352
+ }): QueryBuilderInterface<M>;
202
353
  include(...relations: string[]): QueryBuilderInterface<M>;
203
354
  }
204
355
  /**
@@ -223,11 +374,53 @@ export interface PaginationMeta {
223
374
  * @group Data
224
375
  */
225
376
  export interface FindResult<M extends Record<string, unknown> = Record<string, unknown>> {
226
- /** Array of flat rows matching the query */
227
- data: M[];
377
+ /**
378
+ * Flat rows matching the query, each carrying whatever the query computed
379
+ * for it — see {@link QueryComputedFields}.
380
+ */
381
+ data: (M & QueryComputedFields)[];
228
382
  /** Pagination metadata */
229
383
  meta: PaginationMeta;
230
384
  }
385
+ /**
386
+ * Values a query attaches to a row that are not columns of it.
387
+ *
388
+ * Both are absent unless the query asked for the thing that produces them, so
389
+ * both are optional — and reading one on a query that did not ask returns
390
+ * `undefined` rather than a wrong number.
391
+ *
392
+ * They live here rather than on the row type because a generated row type
393
+ * describes a *table*, and neither of these is in one. Without this, a caller
394
+ * who sorted by relevance could not then read the relevance.
395
+ *
396
+ * A `type` alias, deliberately, not an `interface`. TypeScript grants an
397
+ * implicit index signature to a type alias and withholds it from an interface,
398
+ * so `Row & QueryComputedFields` stops being assignable to
399
+ * `Record<string, unknown>` the moment this becomes an interface. Seven casts
400
+ * in one downstream app broke on exactly that.
401
+ *
402
+ * @group Data
403
+ */
404
+ export type QueryComputedFields = {
405
+ /**
406
+ * Relevance, when the collection declares a {@link SearchConfig} and the
407
+ * query carried a search string. Higher is better; the scale is not
408
+ * comparable between two different search strings.
409
+ */
410
+ _score?: number;
411
+ /**
412
+ * Which declared fields matched, and the text around each hit. Present only
413
+ * when the query asked for it — `.search(term, { explain: true })` — because
414
+ * it costs a `ts_headline` per field per row.
415
+ */
416
+ _matches?: SearchMatch[];
417
+ /**
418
+ * Distance to the query vector, when the query used
419
+ * {@link FindParams.vectorSearch}. Lower is closer, and the rows are
420
+ * already ordered by it.
421
+ */
422
+ _distance?: number;
423
+ };
231
424
  /**
232
425
  * Which column an iteration seeks on, for keyset ("seek") pagination.
233
426
  *
@@ -315,12 +508,27 @@ export type FindAllParams<M extends Record<string, unknown> = Record<string, unk
315
508
  * @group Data
316
509
  */
317
510
  export interface SDKQueryBuilderInterface<M extends Record<string, unknown> = Record<string, unknown>> {
318
- where<K extends keyof M & string>(column: K, operator: WhereFilterOp, value: WhereValue<M[K]>): this;
511
+ where<K extends keyof M & string, Op extends WhereFilterOp>(column: K, operator: Op, value: WhereValueFor<Op, M[K]>): this;
319
512
  where(logicalCondition: LogicalCondition): this;
320
- orderBy(column: keyof M & string, direction?: "asc" | "desc"): this;
513
+ orderBy(column: (keyof M & string) | ComputedSortField, direction?: "asc" | "desc"): this;
321
514
  limit(count: number): this;
322
515
  offset(count: number): this;
323
- search(searchString: string): this;
516
+ search(searchString: string, options?: {
517
+ explain?: boolean;
518
+ }): this;
519
+ /**
520
+ * Order rows by nearest-neighbour distance to `vector`, closest first.
521
+ *
522
+ * Postgres only, over a property declared as `type: "vector"`. Each row
523
+ * comes back with a `_distance`. Any `where` on the same query filters
524
+ * before the ordering; distance decides the order.
525
+ *
526
+ * The query embedding is the caller's to produce.
527
+ */
528
+ vectorSearch(property: string, vector: number[], options?: {
529
+ distance?: "cosine" | "l2" | "inner_product";
530
+ threshold?: number;
531
+ }): this;
324
532
  include(...relations: string[]): this;
325
533
  find(): Promise<FindResult<M>>;
326
534
  count(): Promise<number>;
@@ -365,9 +573,20 @@ export interface WriteOptions {
365
573
  * it performs it again. On a table with a server-assigned id that is a
366
574
  * duplicate row, because the id the client chose was never used.
367
575
  *
576
+ * A key names **one** request, not a job. It records the method, the path
577
+ * and the body it was claimed for, so re-sending that exact request replays
578
+ * its answer, while the same key on a different one is refused with
579
+ * `IDEMPOTENCY_KEY_REUSED` (422) rather than silently answered with the
580
+ * first request's result. Pass a fresh key — a uuid — per call; a reusable
581
+ * business id shared by the create and the delete of one import means the
582
+ * second of them never runs.
583
+ *
368
584
  * Set by the offline queue on every replay. Honoured for 24 hours and scoped
369
- * to the authenticated user; a server that cannot store keys ignores it
370
- * rather than refusing the write.
585
+ * to the authenticated user — an unauthenticated caller has no principal to
586
+ * scope it to, so the key is ignored there. A retry sent while the first
587
+ * attempt is still being answered gets `IDEMPOTENCY_KEY_IN_PROGRESS` (409)
588
+ * and should be sent again. A server that cannot store keys ignores the
589
+ * header rather than refusing the write.
371
590
  */
372
591
  idempotencyKey?: string;
373
592
  }
@@ -467,6 +686,12 @@ export interface SDKCollectionClient<M extends Record<string, unknown> = Record<
467
686
  * Batches are capped server-side (1000 rows by default) because one batch
468
687
  * holds its locks for the whole transaction — chunk larger jobs.
469
688
  *
689
+ * Pass {@link WriteOptions.idempotencyKey} on anything that may be retried.
690
+ * A client that never sees the response cannot know whether the batch
691
+ * committed, and without a key the server cannot tell the retry from a
692
+ * second genuine import — so it performs it again, duplicating every row in
693
+ * the batch rather than just one.
694
+ *
470
695
  * @returns The written rows, in the order given.
471
696
  *
472
697
  * @example
@@ -478,7 +703,7 @@ export interface SDKCollectionClient<M extends Record<string, unknown> = Record<
478
703
  */
479
704
  createMany(data: I[], options?: {
480
705
  upsert?: boolean;
481
- }): Promise<M[]>;
706
+ } & WriteOptions): Promise<M[]>;
482
707
  /**
483
708
  * Update an existing record by ID.
484
709
  * @param data The fields to update (the collection's `Update` shape).
@@ -486,33 +711,120 @@ export interface SDKCollectionClient<M extends Record<string, unknown> = Record<
486
711
  * @throws {RebaseApiError} with status 404 when the record does not exist.
487
712
  */
488
713
  update(id: string | number, data: U): Promise<M>;
714
+ /**
715
+ * Update many records in a single request and a single transaction.
716
+ *
717
+ * The counterpart to {@link createMany}, and the reason it exists is the
718
+ * same: one call per row means one HTTP round trip and one transaction per
719
+ * row. Every record still runs the normal pipeline — callbacks, relations,
720
+ * row-level security — and the batch is all-or-nothing, so a rejected
721
+ * record leaves none of them written and the error names the offending
722
+ * index.
723
+ *
724
+ * Each entry is `{ id, data }` rather than a flat row carrying its own key.
725
+ * That is deliberate: on a table keyed on something other than `id` — a
726
+ * `sku`, a composite key — a flat row cannot say whether a column is the
727
+ * address or a value to write. Naming the address separately mirrors
728
+ * single-row `update(id, data)` exactly and leaves nothing to infer.
729
+ *
730
+ * An id that matches no row fails the batch with a 404 rather than being
731
+ * skipped, for the same reason `update()` does: silently updating four of
732
+ * five rows is worse than updating none.
733
+ *
734
+ * Batches share `createMany`'s server-side cap (1000 rows by default),
735
+ * because one batch holds its locks for the whole transaction.
736
+ *
737
+ * Pass {@link WriteOptions.idempotencyKey} on anything that may be retried.
738
+ * An update replayed in full is naturally idempotent, but one interleaved
739
+ * with another writer's is not — the key is what stops a lost ACK from
740
+ * re-applying a stale batch over newer data.
741
+ *
742
+ * @returns The updated rows, in the order given.
743
+ *
744
+ * @example
745
+ * ```ts
746
+ * await client.data.orders.updateMany([
747
+ * { id: "o-1", data: { status: "shipped" } },
748
+ * { id: "o-2", data: { status: "shipped" } }
749
+ * ]);
750
+ * ```
751
+ */
752
+ updateMany(updates: {
753
+ id: string | number;
754
+ data: U;
755
+ }[], options?: WriteOptions): Promise<M[]>;
489
756
  /**
490
757
  * Delete a record by ID.
491
758
  * @throws {RebaseApiError} with status 404 when the record does not exist.
492
759
  */
493
760
  delete(id: string | number): Promise<void>;
494
761
  /**
495
- * Subscribe to a collection for real-time updates.
762
+ * Delete many records in a single request and a single transaction.
763
+ *
764
+ * Takes ids, not a filter. A filter-shaped bulk delete is a different and
765
+ * far more dangerous operation — the failure mode is an omitted or
766
+ * mistyped condition emptying a table, and it cannot be reviewed at the
767
+ * call site the way an explicit list can. Read first, then pass the ids you
768
+ * meant.
769
+ *
770
+ * `beforeDelete` and `afterDelete` fire per row, exactly as they do for
771
+ * single deletes, and returning `false` from `beforeDelete` fails the batch
772
+ * rather than quietly dropping one row from it. All-or-nothing, so an id
773
+ * that matches no row 404s the whole call.
774
+ *
775
+ * Shares `createMany`'s row cap.
776
+ *
777
+ * @example
778
+ * ```ts
779
+ * const stale = await client.data.sessions.findAll({
780
+ * where: { expires_at: ["<", cutoff] }
781
+ * });
782
+ * await client.data.sessions.deleteMany(stale.map(s => s.id as string));
783
+ * ```
496
784
  */
497
- listen?(params: FindParams<M> | undefined, onUpdate: (response: FindResult<M>) => void, onError?: (error: Error) => void): () => void;
785
+ deleteMany(ids: (string | number)[], options?: WriteOptions): Promise<void>;
498
786
  /**
499
- * Subscribe to a single record for real-time updates.
787
+ * The low-level realtime subscription: raw server pushes, nothing else.
788
+ *
789
+ * **Prefer `observe()`** on a client from `@rebasepro/client`, which wraps
790
+ * this one and is what a UI actually wants — it emits from the local
791
+ * database first when offline is enabled, re-emits on local writes and
792
+ * rollbacks, and de-duplicates emissions so a refresh that changes nothing
793
+ * does not call back. `listen` does none of that; it forwards what the
794
+ * socket sends.
795
+ *
796
+ * Optional because it is only present when realtime is enabled. `observe()`
797
+ * is not — it degrades to a single fetch — which is the other reason to
798
+ * reach for it instead.
500
799
  */
800
+ listen?(params: FindParams<M> | undefined, onUpdate: (response: FindResult<M>) => void, onError?: (error: Error) => void): () => void;
801
+ /** {@link listen} for a single row. Prefer `observeById()`. */
501
802
  listenById?(id: string | number, onUpdate: (row: M | undefined) => void, onError?: (error: Error) => void): () => void;
502
803
  /**
503
804
  * Count the number of records matching the given filter.
504
805
  */
505
806
  count?(params?: FindParams<M>): Promise<number>;
506
- where<K extends keyof M & string>(column: K, operator: WhereFilterOp, value: WhereValue<M[K]>): SDKQueryBuilderInterface<M>;
807
+ where<K extends keyof M & string, Op extends WhereFilterOp>(column: K, operator: Op, value: WhereValueFor<Op, M[K]>): SDKQueryBuilderInterface<M>;
507
808
  where(logicalCondition: LogicalCondition): SDKQueryBuilderInterface<M>;
508
- orderBy(column: keyof M & string, direction?: "asc" | "desc"): SDKQueryBuilderInterface<M>;
809
+ orderBy(column: (keyof M & string) | ComputedSortField, direction?: "asc" | "desc"): SDKQueryBuilderInterface<M>;
509
810
  limit(count: number): SDKQueryBuilderInterface<M>;
510
811
  offset(count: number): SDKQueryBuilderInterface<M>;
511
- search(searchString: string): SDKQueryBuilderInterface<M>;
812
+ search(searchString: string, options?: {
813
+ explain?: boolean;
814
+ }): SDKQueryBuilderInterface<M>;
815
+ /**
816
+ * Order rows by nearest-neighbour distance to `vector`, closest first.
817
+ * Postgres only, over a `type: "vector"` property. See
818
+ * {@link SDKQueryBuilderInterface.vectorSearch}.
819
+ */
820
+ vectorSearch(property: string, vector: number[], options?: {
821
+ distance?: "cosine" | "l2" | "inner_product";
822
+ threshold?: number;
823
+ }): SDKQueryBuilderInterface<M>;
512
824
  include(...relations: string[]): SDKQueryBuilderInterface<M>;
513
825
  }
514
826
  /**
515
- * The unified data access object for the **admin admin** (Entity-shaped).
827
+ * The unified data access object for the **admin panel** (Entity-shaped).
516
828
  *
517
829
  * Access collections as dynamic properties: `data.products.find(...)`. Each
518
830
  * accessor returns `Entity`-wrapped records (`{ id, path, values }`) — the
@@ -521,7 +833,7 @@ export interface SDKCollectionClient<M extends Record<string, unknown> = Record<
521
833
  *
522
834
  * @internal App developers do **not** use this — they use
523
835
  * {@link RebaseSdkData} (flat rows), which is what the SDK client and backend
524
- * `context.data` expose. This Entity-shaped map backs the admin admin only.
836
+ * `context.data` expose. This Entity-shaped map backs the admin panel only.
525
837
  *
526
838
  * @group Data
527
839
  */