@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.
Files changed (71) hide show
  1. package/README.md +4 -0
  2. package/dist/call_context.d.ts +20 -0
  3. package/dist/controllers/client.d.ts +36 -4
  4. package/dist/controllers/data.d.ts +120 -10
  5. package/dist/errors.d.ts +83 -4
  6. package/dist/index.es.js +522 -160
  7. package/dist/index.es.js.map +1 -1
  8. package/dist/types/admin_block.d.ts +2 -2
  9. package/dist/types/auth_adapter.d.ts +41 -6
  10. package/dist/types/backend.d.ts +48 -0
  11. package/dist/types/collections.d.ts +25 -1
  12. package/dist/types/cron.d.ts +34 -0
  13. package/dist/types/database_adapter.d.ts +39 -0
  14. package/dist/types/entity_callbacks.d.ts +14 -1
  15. package/dist/types/filter-operators.d.ts +24 -1
  16. package/dist/types/policy.d.ts +29 -1
  17. package/dist/types/properties.d.ts +216 -3
  18. package/dist/types/relations.d.ts +65 -7
  19. package/dist/types/resource_kinds.d.ts +173 -17
  20. package/dist/types/resources.d.ts +108 -7
  21. package/dist/types/rls-functions.d.ts +11 -0
  22. package/dist/types/storage_source.d.ts +12 -23
  23. package/package.json +24 -23
  24. package/src/call_context.ts +0 -120
  25. package/src/controllers/auth_state.ts +0 -24
  26. package/src/controllers/client.ts +0 -494
  27. package/src/controllers/collection_registry.ts +0 -62
  28. package/src/controllers/data.ts +0 -1012
  29. package/src/controllers/data_driver.ts +0 -576
  30. package/src/controllers/effective_role.ts +0 -4
  31. package/src/controllers/email.ts +0 -91
  32. package/src/controllers/index.ts +0 -11
  33. package/src/controllers/storage.ts +0 -252
  34. package/src/errors.ts +0 -119
  35. package/src/index.ts +0 -5
  36. package/src/types/admin_block.ts +0 -209
  37. package/src/types/api_keys.ts +0 -108
  38. package/src/types/auth_adapter.ts +0 -580
  39. package/src/types/backend.ts +0 -987
  40. package/src/types/backup.ts +0 -26
  41. package/src/types/channel_bus.ts +0 -202
  42. package/src/types/chips.ts +0 -34
  43. package/src/types/collection_contract.ts +0 -278
  44. package/src/types/collections.ts +0 -763
  45. package/src/types/component_ref.ts +0 -92
  46. package/src/types/cron.ts +0 -213
  47. package/src/types/data_source.ts +0 -357
  48. package/src/types/database_adapter.ts +0 -267
  49. package/src/types/entities.ts +0 -226
  50. package/src/types/entity_callbacks.ts +0 -229
  51. package/src/types/filter-operators.ts +0 -444
  52. package/src/types/history.ts +0 -66
  53. package/src/types/index.ts +0 -36
  54. package/src/types/indexes.ts +0 -180
  55. package/src/types/policy.ts +0 -328
  56. package/src/types/postgres_introspection.ts +0 -101
  57. package/src/types/project_manifest.ts +0 -598
  58. package/src/types/properties.ts +0 -1368
  59. package/src/types/relations.ts +0 -417
  60. package/src/types/resource_kinds.ts +0 -390
  61. package/src/types/resources.ts +0 -368
  62. package/src/types/rls-functions.ts +0 -98
  63. package/src/types/schema_editing.ts +0 -157
  64. package/src/types/schema_version.ts +0 -112
  65. package/src/types/search.ts +0 -247
  66. package/src/types/security_rules.ts +0 -344
  67. package/src/types/storage_authorize.ts +0 -77
  68. package/src/types/storage_source.ts +0 -248
  69. package/src/types/websockets.ts +0 -117
  70. package/src/users/index.ts +0 -2
  71. package/src/users/user.ts +0 -69
@@ -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
- );