@rebasepro/types 0.8.0 → 0.9.1-canary.09aaf62

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 (88) hide show
  1. package/README.md +13 -13
  2. package/dist/controllers/auth.d.ts +7 -1
  3. package/dist/controllers/client.d.ts +204 -6
  4. package/dist/controllers/collection_registry.d.ts +3 -3
  5. package/dist/controllers/customization_controller.d.ts +1 -1
  6. package/dist/controllers/data.d.ts +309 -35
  7. package/dist/controllers/data_driver.d.ts +92 -37
  8. package/dist/controllers/email.d.ts +2 -2
  9. package/dist/controllers/index.d.ts +1 -1
  10. package/dist/controllers/local_config_persistence.d.ts +4 -4
  11. package/dist/controllers/navigation.d.ts +2 -2
  12. package/dist/controllers/registry.d.ts +21 -8
  13. package/dist/controllers/{side_entity_controller.d.ts → side_panel_controller.d.ts} +7 -7
  14. package/dist/controllers/storage.d.ts +39 -0
  15. package/dist/errors.d.ts +64 -0
  16. package/dist/index.d.ts +1 -0
  17. package/dist/index.es.js +160 -15
  18. package/dist/index.es.js.map +1 -1
  19. package/dist/rebase_context.d.ts +8 -4
  20. package/dist/types/auth_adapter.d.ts +29 -4
  21. package/dist/types/backend.d.ts +33 -26
  22. package/dist/types/backup.d.ts +20 -0
  23. package/dist/types/builders.d.ts +2 -2
  24. package/dist/types/collections.d.ts +84 -75
  25. package/dist/types/component_overrides.d.ts +61 -3
  26. package/dist/types/cron.d.ts +11 -2
  27. package/dist/types/data_source.d.ts +15 -2
  28. package/dist/types/database_adapter.d.ts +21 -4
  29. package/dist/types/entities.d.ts +7 -7
  30. package/dist/types/entity_actions.d.ts +7 -7
  31. package/dist/types/entity_callbacks.d.ts +39 -39
  32. package/dist/types/entity_views.d.ts +3 -3
  33. package/dist/types/filter-operators.d.ts +62 -17
  34. package/dist/types/index.d.ts +1 -0
  35. package/dist/types/modify_collections.d.ts +2 -2
  36. package/dist/types/plugins.d.ts +9 -9
  37. package/dist/types/policy.d.ts +111 -11
  38. package/dist/types/properties.d.ts +54 -9
  39. package/dist/types/relations.d.ts +3 -3
  40. package/dist/types/slots.d.ts +14 -14
  41. package/dist/types/translations.d.ts +5 -1
  42. package/dist/types/user_management_delegate.d.ts +8 -2
  43. package/dist/types/websockets.d.ts +46 -13
  44. package/dist/users/user.d.ts +21 -9
  45. package/package.json +5 -6
  46. package/src/controllers/auth.tsx +7 -1
  47. package/src/controllers/client.ts +235 -7
  48. package/src/controllers/collection_registry.ts +3 -3
  49. package/src/controllers/customization_controller.tsx +1 -1
  50. package/src/controllers/data.ts +330 -39
  51. package/src/controllers/data_driver.ts +93 -40
  52. package/src/controllers/email.ts +2 -2
  53. package/src/controllers/index.ts +1 -1
  54. package/src/controllers/local_config_persistence.tsx +4 -4
  55. package/src/controllers/navigation.ts +2 -2
  56. package/src/controllers/registry.ts +22 -8
  57. package/src/controllers/{side_entity_controller.tsx → side_panel_controller.tsx} +7 -7
  58. package/src/controllers/storage.ts +60 -1
  59. package/src/errors.ts +80 -0
  60. package/src/index.ts +1 -0
  61. package/src/rebase_context.tsx +8 -4
  62. package/src/types/auth_adapter.ts +29 -4
  63. package/src/types/backend.ts +44 -36
  64. package/src/types/backup.ts +26 -0
  65. package/src/types/builders.ts +2 -2
  66. package/src/types/collections.ts +101 -91
  67. package/src/types/component_overrides.ts +72 -5
  68. package/src/types/cron.ts +11 -2
  69. package/src/types/data_source.ts +24 -2
  70. package/src/types/database_adapter.ts +19 -4
  71. package/src/types/entities.ts +7 -7
  72. package/src/types/entity_actions.tsx +7 -7
  73. package/src/types/entity_callbacks.ts +42 -42
  74. package/src/types/entity_views.tsx +3 -3
  75. package/src/types/filter-operators.ts +96 -23
  76. package/src/types/index.ts +1 -0
  77. package/src/types/modify_collections.tsx +2 -2
  78. package/src/types/plugins.tsx +9 -9
  79. package/src/types/policy.ts +114 -9
  80. package/src/types/properties.ts +58 -9
  81. package/src/types/relations.ts +3 -3
  82. package/src/types/slots.tsx +14 -14
  83. package/src/types/translations.ts +5 -1
  84. package/src/types/user_management_delegate.ts +8 -2
  85. package/src/types/websockets.ts +43 -14
  86. package/src/users/user.ts +22 -9
  87. package/dist/index.umd.js +0 -458
  88. package/dist/index.umd.js.map +0 -1
@@ -1,5 +1,5 @@
1
1
  import { Entity, EntityValues } from "../types/entities";
2
- import { WhereFilterOp, FilterValues } from "../types/filter-operators";
2
+ import { WhereFilterOp, FilterValues, OrderByTuple } from "../types/filter-operators";
3
3
  export type WhereValue<T> = T | T[] | null;
4
4
  export interface LogicalCondition {
5
5
  type: "and" | "or";
@@ -13,19 +13,51 @@ export interface FilterCondition {
13
13
  /**
14
14
  * Parameters for querying a collection.
15
15
  *
16
+ * ## How the filter parameters combine
17
+ *
18
+ * `where`, `logical`, and `searchString` are **independent** and, when more
19
+ * than one is present, are combined with **AND** — every clause must match.
20
+ * Concretely the backend builds:
21
+ *
22
+ * ```text
23
+ * (where filters, AND-ed together)
24
+ * AND (logical group)
25
+ * AND (searchString matches, OR-ed across searchable columns)
26
+ * ```
27
+ *
28
+ * So `where` does **not** conflict with or override `logical` — they stack.
29
+ * If you need `where` fields OR-ed with each other, move them into `logical`
30
+ * instead. There is no way to OR `where` against `logical`; express anything
31
+ * that isn't a plain AND of the three groups inside a single `logical` tree.
32
+ *
33
+ * ## Pagination precedence
34
+ *
35
+ * `limit`/`offset` and `page` describe the same window two ways. If **both
36
+ * `offset` and `page` are provided, `page` wins** — the backend computes
37
+ * `offset = (page - 1) * (limit ?? 20)` and ignores the explicit `offset`.
38
+ * Pick one style per query.
39
+ *
16
40
  * @group Data
17
41
  */
18
42
  export interface FindParams {
19
- /** Maximum number of items to return (default: 20) */
43
+ /** Maximum number of items to return (default: 20). */
20
44
  limit?: number;
21
- /** Number of items to skip */
45
+ /**
46
+ * Number of items to skip. Ignored when {@link FindParams.page} is also
47
+ * set — `page` takes precedence.
48
+ */
22
49
  offset?: number;
23
- /** Page number (1-indexed), alternative to offset */
50
+ /**
51
+ * Page number (1-indexed), alternative to {@link FindParams.offset}.
52
+ * When set, overrides `offset` as `(page - 1) * (limit ?? 20)`.
53
+ */
24
54
  page?: number;
25
55
  /**
26
56
  * Filter conditions keyed by field name.
27
57
  * Each value is a `[WhereFilterOp, value]` tuple or an array of tuples
28
- * for multiple conditions on the same field.
58
+ * for multiple conditions on the same field. Multiple fields, and multiple
59
+ * tuples on one field, are **AND-ed**; also AND-ed with `logical` and
60
+ * `searchString` when present (see the interface docs).
29
61
  *
30
62
  * @example
31
63
  * { status: ["==", "active"] }
@@ -34,16 +66,24 @@ export interface FindParams {
34
66
  * { age: [[">=", 18], ["<", 65]] }
35
67
  */
36
68
  where?: FilterValues<string>;
37
- /** Logical grouping conditions (AND/OR) */
69
+ /**
70
+ * Logical grouping conditions (AND/OR). Use this for anything `where`
71
+ * can't express — notably OR-ing conditions. AND-ed with `where` and
72
+ * `searchString` when present (see the interface docs).
73
+ */
38
74
  logical?: LogicalCondition;
39
75
  /**
40
- * Sort order. Format: "field:direction".
41
- * @example "created_at:desc", "name:asc"
76
+ * Sort order as a `[field, direction]` tuple.
77
+ * @example orderBy: ["created_at", "desc"]
42
78
  */
43
- orderBy?: string;
79
+ orderBy?: OrderByTuple;
44
80
  /** Relations to include in the response */
45
81
  include?: string[];
46
- /** Full-text search string */
82
+ /**
83
+ * Full-text search string. Matched (OR-ed) across the collection's
84
+ * searchable columns, then AND-ed with `where`/`logical`. This is the
85
+ * value behind the query builder's `.search()` method.
86
+ */
47
87
  searchString?: string;
48
88
  }
49
89
  /**
@@ -62,7 +102,13 @@ export interface FindResponse<M extends Record<string, unknown> = Record<string,
62
102
  };
63
103
  }
64
104
  /**
65
- * Fluent Query Builder Interface supported on both client and server accessors.
105
+ * Fluent query builder for the **admin CMS** resolves to `FindResponse<M>`
106
+ * (Snapshot-wrapped rows).
107
+ *
108
+ * @internal App developers should use {@link SDKQueryBuilderInterface}
109
+ * (flat rows, returned by `client.data.*` / `context.data.*`). This
110
+ * Snapshot-flavored variant backs the admin CMS internals only.
111
+ *
66
112
  * @group Data
67
113
  */
68
114
  export interface QueryBuilderInterface<M extends Record<string, unknown> = Record<string, unknown>> {
@@ -77,11 +123,13 @@ export interface QueryBuilderInterface<M extends Record<string, unknown> = Recor
77
123
  listen(onUpdate: (data: FindResponse<M>) => void, onError?: (error: Error) => void): () => void;
78
124
  }
79
125
  /**
80
- * A single collection's CRUD accessor.
126
+ * A single collection's CRUD accessor for the **admin CMS** — every method
127
+ * resolves to `Snapshot`-wrapped rows (`FindResponse<M>` / `Snapshot<M>`).
81
128
  *
82
- * This is the unified API surface used in both:
83
- * - The generated SDK (`client.data.products.create(...)`)
84
- * - Framework callbacks (`context.data.products.create(...)`)
129
+ * @internal App developers do **not** use this. The public, symmetric surface
130
+ * is {@link SDKCollectionClient} (flat rows), exposed as `client.data.products`
131
+ * in the SDK and `context.data.products` in framework callbacks. This
132
+ * Snapshot-flavored accessor backs the admin CMS view-model only.
85
133
  *
86
134
  * @group Data
87
135
  */
@@ -101,6 +149,15 @@ export interface CollectionAccessor<M extends Record<string, unknown> = Record<s
101
149
  * @returns The created entity
102
150
  */
103
151
  create(data: Partial<EntityValues<M>>, id?: string | number): Promise<Entity<M>>;
152
+ /**
153
+ * Create many records in a single transaction.
154
+ *
155
+ * See {@link SDKCollectionClient.createMany}. Optional: not every driver can
156
+ * write in bulk, and callers should fall back to `create` per record.
157
+ */
158
+ createMany?(data: Partial<EntityValues<M>>[], options?: {
159
+ upsert?: boolean;
160
+ }): Promise<Entity<M>[]>;
104
161
  /**
105
162
  * Update an existing record by ID.
106
163
  * @returns The updated entity
@@ -110,10 +167,6 @@ export interface CollectionAccessor<M extends Record<string, unknown> = Record<s
110
167
  * Delete a record by ID.
111
168
  */
112
169
  delete(id: string | number): Promise<void>;
113
- /**
114
- * Delete all records in this collection.
115
- */
116
- deleteAll?(): Promise<void>;
117
170
  /**
118
171
  * Subscribe to a collection for real-time updates.
119
172
  * Optional method, may not be supported by all implementations (like stateless HTTP clients).
@@ -137,27 +190,171 @@ export interface CollectionAccessor<M extends Record<string, unknown> = Record<s
137
190
  include(...relations: string[]): QueryBuilderInterface<M>;
138
191
  }
139
192
  /**
140
- * The unified data access object.
193
+ * Pagination metadata returned with collection queries.
194
+ * @group Data
195
+ */
196
+ export interface PaginationMeta {
197
+ total: number;
198
+ limit: number;
199
+ offset: number;
200
+ hasMore: boolean;
201
+ }
202
+ /**
203
+ * Paginated response from a collection query (SDK-facing).
204
+ * Returns flat rows instead of Entity-wrapped objects.
205
+ *
206
+ * @example
207
+ * const { data, meta } = await rebase.data.posts.find();
208
+ * console.log(data[0].title); // direct access — no .values
209
+ * console.log(meta.total);
210
+ *
211
+ * @group Data
212
+ */
213
+ export interface FindResult<M extends Record<string, unknown> = Record<string, unknown>> {
214
+ /** Array of flat rows matching the query */
215
+ data: M[];
216
+ /** Pagination metadata */
217
+ meta: PaginationMeta;
218
+ }
219
+ /**
220
+ * Fluent Query Builder Interface for the SDK client.
221
+ * Returns `FindResult<M>` (flat rows) instead of `FindResponse<M>` (Entity-wrapped).
222
+ *
223
+ * @group Data
224
+ */
225
+ export interface SDKQueryBuilderInterface<M extends Record<string, unknown> = Record<string, unknown>> {
226
+ where<K extends keyof M & string>(column: K, operator: WhereFilterOp, value: WhereValue<M[K]>): this;
227
+ where(logicalCondition: LogicalCondition): this;
228
+ orderBy(column: keyof M & string, direction?: "asc" | "desc"): this;
229
+ limit(count: number): this;
230
+ offset(count: number): this;
231
+ search(searchString: string): this;
232
+ include(...relations: string[]): this;
233
+ find(): Promise<FindResult<M>>;
234
+ count(): Promise<number>;
235
+ listen(onUpdate: (data: FindResult<M>) => void, onError?: (error: Error) => void): () => void;
236
+ }
237
+ /**
238
+ * SDK collection client — returns flat rows, no Entity wrapper.
141
239
  *
142
- * Access collections as dynamic properties: `data.products.find(...)`.
143
- * In the SDK this is backed by HTTP transport (typed, generated per-project).
144
- * In the framework this is backed by a Proxy + in-process database driver (dynamic).
240
+ * This is the public API surface for app developers using
241
+ * `createRebaseClient()`. CMS internals use `CollectionAccessor` instead.
242
+ *
243
+ * Type parameters:
244
+ * - `M` — the **Row** shape returned by reads (`find`, `findById`, `listen`).
245
+ * - `I` — the **Insert** shape accepted by {@link create}. Defaults to
246
+ * `Partial<M>`; the generated SDK supplies a dedicated `Insert` type where
247
+ * required columns are required and auto-generated / read-only columns are
248
+ * omitted, so `create({})` on a table with required fields is a compile error.
249
+ * - `U` — the **Update** shape accepted by {@link update}. Defaults to
250
+ * `Partial<M>`; the generated SDK supplies a dedicated `Update` type.
145
251
  *
146
252
  * @example
147
- * // SDK
148
- * const client = createRebaseClient({ baseUrl: "..." });
149
- * await client.data.products.create({ name: "Camera", price: 299 });
253
+ * const { data: posts } = await rebase.data.posts.find();
254
+ * console.log(posts[0].title); // flat access
255
+ * console.log(posts[0].id); // id at top level
150
256
  *
151
- * // Framework callback
152
- * callbacks: {
153
- * afterSave({ context }) {
154
- * await context.data.logs.create({ action: "saved", timestamp: new Date() });
155
- * }
156
- * }
257
+ * const post = await rebase.data.posts.findById(1);
258
+ * console.log(post?.title); // no .values needed
157
259
  *
158
260
  * @group Data
159
261
  */
160
- export interface RebaseData {
262
+ export interface SDKCollectionClient<M extends Record<string, unknown> = Record<string, unknown>, I = Partial<M>, U = Partial<M>> {
263
+ /**
264
+ * Find multiple records with optional filtering, pagination, and sorting.
265
+ */
266
+ find(params?: FindParams): Promise<FindResult<M>>;
267
+ /**
268
+ * Find a single record by its ID.
269
+ */
270
+ findById(id: string | number): Promise<M | undefined>;
271
+ /**
272
+ * Create a new record.
273
+ * @param data The record data to create (the collection's `Insert` shape).
274
+ * @param id Optional specific id, sent as an `id` column. This is for tables
275
+ * whose key *is* `id`: the value goes in as that column. For a table keyed
276
+ * on anything else (a `sku`, a composite key), there is no `id` column to
277
+ * receive it — put the key in `data` instead, where it belongs among the
278
+ * columns.
279
+ * @returns The created row
280
+ */
281
+ create(data: I, id?: string | number): Promise<M>;
282
+ /**
283
+ * Write many records in a single request and a single transaction.
284
+ *
285
+ * Built for imports and ETL, where one call per row means one HTTP round
286
+ * trip and one transaction per row. Every record still runs the normal
287
+ * pipeline — callbacks, relations, row-level security — and the batch is
288
+ * all-or-nothing: if any record is rejected, none of them land and the
289
+ * error names the offending index.
290
+ *
291
+ * A record carrying its primary key updates that row; one without inserts.
292
+ * With `{ upsert: true }` each record is written as INSERT ... ON CONFLICT
293
+ * DO UPDATE on the primary key instead, which is what makes a re-runnable
294
+ * import idempotent.
295
+ *
296
+ * Batches are capped server-side (1000 rows by default) because one batch
297
+ * holds its locks for the whole transaction — chunk larger jobs.
298
+ *
299
+ * @returns The written rows, in the order given.
300
+ *
301
+ * @example
302
+ * ```ts
303
+ * for (const chunk of chunks(rows, 1000)) {
304
+ * await client.data.products.createMany(chunk, { upsert: true });
305
+ * }
306
+ * ```
307
+ */
308
+ createMany(data: I[], options?: {
309
+ upsert?: boolean;
310
+ }): Promise<M[]>;
311
+ /**
312
+ * Update an existing record by ID.
313
+ * @param data The fields to update (the collection's `Update` shape).
314
+ * @returns The updated row.
315
+ * @throws {RebaseApiError} with status 404 when the record does not exist.
316
+ */
317
+ update(id: string | number, data: U): Promise<M>;
318
+ /**
319
+ * Delete a record by ID.
320
+ * @throws {RebaseApiError} with status 404 when the record does not exist.
321
+ */
322
+ delete(id: string | number): Promise<void>;
323
+ /**
324
+ * Subscribe to a collection for real-time updates.
325
+ */
326
+ listen?(params: FindParams | undefined, onUpdate: (response: FindResult<M>) => void, onError?: (error: Error) => void): () => void;
327
+ /**
328
+ * Subscribe to a single record for real-time updates.
329
+ */
330
+ listenById?(id: string | number, onUpdate: (row: M | undefined) => void, onError?: (error: Error) => void): () => void;
331
+ /**
332
+ * Count the number of records matching the given filter.
333
+ */
334
+ count?(params?: FindParams): Promise<number>;
335
+ where<K extends keyof M & string>(column: K, operator: WhereFilterOp, value: WhereValue<M[K]>): SDKQueryBuilderInterface<M>;
336
+ where(logicalCondition: LogicalCondition): SDKQueryBuilderInterface<M>;
337
+ orderBy(column: keyof M & string, direction?: "asc" | "desc"): SDKQueryBuilderInterface<M>;
338
+ limit(count: number): SDKQueryBuilderInterface<M>;
339
+ offset(count: number): SDKQueryBuilderInterface<M>;
340
+ search(searchString: string): SDKQueryBuilderInterface<M>;
341
+ include(...relations: string[]): SDKQueryBuilderInterface<M>;
342
+ }
343
+ /**
344
+ * The unified data access object for the **admin CMS** (Entity-shaped).
345
+ *
346
+ * Access collections as dynamic properties: `data.products.find(...)`. Each
347
+ * accessor returns `Entity`-wrapped records (`{ id, path, values }`) — the
348
+ * view-model the CMS renders. This is what `useData()` / the admin
349
+ * `RebaseContext.data` are backed by.
350
+ *
351
+ * @internal App developers do **not** use this — they use
352
+ * {@link RebaseSdkData} (flat rows), which is what the SDK client and backend
353
+ * `context.data` expose. This Entity-shaped map backs the admin CMS only.
354
+ *
355
+ * @group Data
356
+ */
357
+ export type RebaseData<DB = unknown> = {
161
358
  /**
162
359
  * Get a collection accessor by slug.
163
360
  * Alternative to dynamic property access for cases where
@@ -168,12 +365,89 @@ export interface RebaseData {
168
365
  * await accessor.find({ limit: 10 });
169
366
  */
170
367
  collection<M extends Record<string, unknown> = Record<string, unknown>>(slug: string): CollectionAccessor<M>;
368
+ } & (DB extends Record<string, unknown> ? {
369
+ [K in keyof DB]: CollectionAccessor<DB[K] extends {
370
+ Row: infer R extends Record<string, unknown>;
371
+ } ? R : Record<string, unknown>>;
372
+ } : {
171
373
  /**
172
374
  * Dynamic collection accessor.
173
375
  * Access any collection by its slug as a property.
174
376
  *
175
377
  * @example
176
- * data.products.find({ where: { status: "eq.published" } })
378
+ * data.products.find({ where: { status: ["==", "published"] } })
177
379
  */
178
380
  [collectionSlug: string]: CollectionAccessor | ((slug: string) => CollectionAccessor);
179
- }
381
+ });
382
+ /**
383
+ * The unified data access object for the **SDK** — flat rows, no Entity wrapper.
384
+ *
385
+ * This is the symmetric developer-facing data API, identical in shape on both
386
+ * sides of the stack:
387
+ * - The frontend SDK client (`client.data.products.find()`)
388
+ * - Backend framework callbacks & scripts (`context.data.products.find()`)
389
+ *
390
+ * Every accessor returns flat rows (the table's columns) via
391
+ * {@link SDKCollectionClient} — access fields directly (`row.title`), never
392
+ * `row.values.title`. The admin CMS uses {@link RebaseData} (Entity) instead.
393
+ *
394
+ * @example
395
+ * // Frontend SDK
396
+ * const { data: posts } = await client.data.posts.find();
397
+ * console.log(posts[0].title); // flat — no .values
398
+ *
399
+ * // Backend callback — identical shape
400
+ * callbacks: {
401
+ * beforeSave: async ({ context }) => {
402
+ * const product = await context.data.products.findById(id);
403
+ * console.log(product?.price); // flat — no .values
404
+ * }
405
+ * }
406
+ *
407
+ * @group Data
408
+ */
409
+ /**
410
+ * Extract the `Row` shape from a generated `Database[slug]` entry, falling
411
+ * back to an open record when the entry is untyped.
412
+ * @group Data
413
+ */
414
+ export type RowOf<T> = T extends {
415
+ Row: infer R extends Record<string, unknown>;
416
+ } ? R : Record<string, unknown>;
417
+ /**
418
+ * Extract the `Insert` shape from a generated `Database[slug]` entry (the
419
+ * input accepted by `create`), falling back to `Partial<Row>`.
420
+ * @group Data
421
+ */
422
+ export type InsertOf<T> = T extends {
423
+ Insert: infer I extends Record<string, unknown>;
424
+ } ? I : Partial<RowOf<T>>;
425
+ /**
426
+ * Extract the `Update` shape from a generated `Database[slug]` entry (the
427
+ * input accepted by `update`), falling back to `Partial<Row>`.
428
+ * @group Data
429
+ */
430
+ export type UpdateOf<T> = T extends {
431
+ Update: infer U extends Record<string, unknown>;
432
+ } ? U : Partial<RowOf<T>>;
433
+ export type RebaseSdkData<DB = unknown> = {
434
+ /**
435
+ * Get a flat collection accessor by slug.
436
+ *
437
+ * @example
438
+ * const accessor = data.collection("products");
439
+ * await accessor.find({ limit: 10 });
440
+ */
441
+ collection<M extends Record<string, unknown> = Record<string, unknown>>(slug: string): SDKCollectionClient<M>;
442
+ } & (DB extends Record<string, unknown> ? {
443
+ [K in keyof DB]: SDKCollectionClient<RowOf<DB[K]>, InsertOf<DB[K]>, UpdateOf<DB[K]>>;
444
+ } : {
445
+ /**
446
+ * Dynamic flat collection accessor.
447
+ * Access any collection by its slug as a property.
448
+ *
449
+ * @example
450
+ * data.products.find({ where: { status: ["==", "published"] } })
451
+ */
452
+ [collectionSlug: string]: SDKCollectionClient | ((slug: string) => SDKCollectionClient);
453
+ });
@@ -1,20 +1,20 @@
1
- import type { Entity, EntityStatus, EntityValues } from "../types/entities";
2
- import type { EntityCollection, FilterValues } from "../types/collections";
1
+ import type { EntityStatus, EntityValues } from "../types/entities";
2
+ import type { CollectionConfig, FilterValues } from "../types/collections";
3
3
  import type { RebaseContext } from "../rebase_context";
4
4
  /**
5
5
  * @internal
6
6
  */
7
- export interface FetchEntityProps<M extends Record<string, unknown> = Record<string, unknown>> {
7
+ export interface FetchOneProps<M extends Record<string, unknown> = Record<string, unknown>> {
8
8
  path: string;
9
- entityId: string | number;
9
+ id: string | number;
10
10
  databaseId?: string;
11
- collection?: EntityCollection<M>;
11
+ collection?: CollectionConfig<M>;
12
12
  }
13
13
  /**
14
14
  * @internal
15
15
  */
16
- export type ListenEntityProps<M extends Record<string, unknown> = Record<string, unknown>> = FetchEntityProps<M> & {
17
- onUpdate: (entity: Entity<M> | null) => void;
16
+ export type ListenOneProps<M extends Record<string, unknown> = Record<string, unknown>> = FetchOneProps<M> & {
17
+ onUpdate: (row: Record<string, unknown> | null) => void;
18
18
  onError?: (error: Error) => void;
19
19
  };
20
20
  /**
@@ -37,7 +37,7 @@ export interface VectorSearchParams {
37
37
  */
38
38
  export interface FetchCollectionProps<M extends Record<string, unknown> = Record<string, unknown>> {
39
39
  path: string;
40
- collection?: EntityCollection<M>;
40
+ collection?: CollectionConfig<M>;
41
41
  filter?: FilterValues<Extract<keyof M, string>>;
42
42
  limit?: number;
43
43
  offset?: number;
@@ -52,38 +52,74 @@ export interface FetchCollectionProps<M extends Record<string, unknown> = Record
52
52
  * @internal
53
53
  */
54
54
  export type ListenCollectionProps<M extends Record<string, unknown> = Record<string, unknown>> = FetchCollectionProps<M> & {
55
- onUpdate: (entities: Entity<M>[]) => void;
55
+ onUpdate: (rows: Record<string, unknown>[]) => void;
56
56
  onError?: (error: Error) => void;
57
57
  };
58
58
  /**
59
59
  * @internal
60
60
  */
61
- export interface SaveEntityProps<M extends Record<string, unknown> = Record<string, unknown>> {
61
+ export interface SaveProps<M extends Record<string, unknown> = Record<string, unknown>> {
62
62
  path: string;
63
63
  values: Partial<EntityValues<M>>;
64
- entityId?: string | number;
64
+ id?: string | number;
65
65
  previousValues?: Partial<EntityValues<M>>;
66
- collection?: EntityCollection<M>;
66
+ collection?: CollectionConfig<M>;
67
67
  status: EntityStatus;
68
+ /**
69
+ * Write the row with INSERT ... ON CONFLICT DO UPDATE on the primary key
70
+ * instead of choosing between insert and update up front.
71
+ *
72
+ * One statement, so it does not lose the race a read-then-write can, and it
73
+ * succeeds whether or not the row is already there — what a re-runnable
74
+ * import needs. Requires every primary key column to be present; without
75
+ * them there is no conflict target and the row is inserted normally.
76
+ */
77
+ upsert?: boolean;
68
78
  }
69
79
  /**
70
80
  * @internal
71
81
  */
72
- export interface DeleteEntityProps<M extends Record<string, unknown> = Record<string, unknown>> {
73
- entity: Entity<M>;
74
- collection?: EntityCollection<M>;
82
+ export interface SaveManyProps<M extends Record<string, unknown> = Record<string, unknown>> {
83
+ path: string;
84
+ /**
85
+ * The rows to write. A row carrying its primary key updates (or, with
86
+ * `upsert`, inserts-or-updates) that row; one without inserts.
87
+ */
88
+ rows: Partial<EntityValues<M>>[];
89
+ collection?: CollectionConfig<M>;
90
+ /** Apply every row as INSERT ... ON CONFLICT DO UPDATE. See {@link SaveProps.upsert}. */
91
+ upsert?: boolean;
92
+ }
93
+ /**
94
+ * @internal
95
+ */
96
+ export interface DeleteProps<M extends Record<string, unknown> = Record<string, unknown>> {
97
+ row: {
98
+ id: string | number;
99
+ path: string;
100
+ values?: Partial<EntityValues<M>>;
101
+ };
102
+ collection?: CollectionConfig<M>;
75
103
  }
76
104
  export type FilterCombinationValidProps = {
77
105
  path: string;
78
106
  databaseId?: string;
79
- collection: EntityCollection;
107
+ collection: CollectionConfig;
80
108
  filterValues: FilterValues<string>;
81
109
  sortBy?: [string, "asc" | "desc"];
82
110
  };
83
111
  /**
84
- * Internal driver interface for communicating with the data layer.
85
- * This is NOT the public API — use `RebaseData` / `context.data` instead.
86
- * @internal
112
+ * The integration SPI for plugging a data backend into Rebase.
113
+ *
114
+ * Implement this interface to connect a custom backend (or use a built-in
115
+ * driver such as the Firestore one) and register it on
116
+ * `<Rebase dataSources>`. Rebase wraps drivers via `buildRebaseData` and
117
+ * routes collections to them by their `dataSource` key.
118
+ *
119
+ * For *consuming* data in application code, use `RebaseData` /
120
+ * `context.data` instead — this interface is only for providing it.
121
+ *
122
+ * @group Datasource
87
123
  */
88
124
  export interface DataDriver {
89
125
  /**
@@ -97,9 +133,9 @@ export interface DataDriver {
97
133
  /**
98
134
  * Fetch data from a collection
99
135
  * @param props
100
- * @return Promise of entities
136
+ * @return Promise of flat rows
101
137
  */
102
- fetchCollection<M extends Record<string, unknown> = Record<string, unknown>>(props: FetchCollectionProps<M>): Promise<Entity<M>[]>;
138
+ fetchCollection<M extends Record<string, unknown> = Record<string, unknown>>(props: FetchCollectionProps<M>): Promise<Record<string, unknown>[]>;
103
139
  /**
104
140
  * Listen to a collection in a given path. If you don't implement this method
105
141
  * `fetchCollection` will be used instead, with no real time updates.
@@ -108,27 +144,40 @@ export interface DataDriver {
108
144
  */
109
145
  listenCollection?<M extends Record<string, unknown> = Record<string, unknown>>(props: ListenCollectionProps<M>): () => void;
110
146
  /**
111
- * Retrieve an entity given a path and a collection
147
+ * Retrieve a single row given a path and a collection
112
148
  * @param props
113
149
  */
114
- fetchEntity<M extends Record<string, unknown> = Record<string, unknown>>(props: FetchEntityProps<M>): Promise<Entity<M> | undefined>;
150
+ fetchOne<M extends Record<string, unknown> = Record<string, unknown>>(props: FetchOneProps<M>): Promise<Record<string, unknown> | undefined>;
115
151
  /**
116
- * Get realtime updates on one entity.
152
+ * Get realtime updates on one row.
117
153
  * @param props
118
154
  * @return Function to cancel subscription
119
155
  */
120
- listenEntity?<M extends Record<string, unknown> = Record<string, unknown>>(props: ListenEntityProps<M>): () => void;
156
+ listenOne?<M extends Record<string, unknown> = Record<string, unknown>>(props: ListenOneProps<M>): () => void;
121
157
  /**
122
- * Save entity to the specified path
158
+ * Save a row to the specified path
123
159
  * @param props
124
160
  */
125
- saveEntity<M extends Record<string, unknown> = Record<string, unknown>>(props: SaveEntityProps<M>): Promise<Entity<M>>;
161
+ save<M extends Record<string, unknown> = Record<string, unknown>>(props: SaveProps<M>): Promise<Record<string, unknown>>;
162
+ /**
163
+ * Save many rows as one unit of work.
164
+ *
165
+ * Every row runs the same pipeline as {@link save} — callbacks, relations
166
+ * and row-level security all still apply — but they share a single
167
+ * transaction, so the batch either lands whole or not at all. That, and the
168
+ * single round trip, is what makes importing tens of thousands of rows
169
+ * viable without dropping to raw SQL.
170
+ *
171
+ * Optional: drivers that cannot do this leave it undefined and callers fall
172
+ * back to `save` per row.
173
+ */
174
+ saveMany?<M extends Record<string, unknown> = Record<string, unknown>>(props: SaveManyProps<M>): Promise<Record<string, unknown>[]>;
126
175
  /**
127
- * Delete an entity
176
+ * Delete a entity
128
177
  * @param props
129
178
  * @return was the whole deletion flow successful
130
179
  */
131
- deleteEntity<M extends Record<string, unknown> = Record<string, unknown>>(props: DeleteEntityProps<M>): Promise<void>;
180
+ delete<M extends Record<string, unknown> = Record<string, unknown>>(props: DeleteProps<M>): Promise<void>;
132
181
  /**
133
182
  * Delete all entities from a collection.
134
183
  * @param path Collection path
@@ -139,15 +188,15 @@ export interface DataDriver {
139
188
  * @param path Collection path
140
189
  * @param name of the property
141
190
  * @param value
142
- * @param entityId
191
+ * @param id
143
192
  * @param collection
144
193
  * @return `true` if there are no other fields besides the given entity
145
194
  */
146
- checkUniqueField(path: string, name: string, value: unknown, entityId?: string | number, collection?: EntityCollection): Promise<boolean>;
195
+ checkUniqueField(path: string, name: string, value: unknown, id?: string | number, collection?: CollectionConfig): Promise<boolean>;
147
196
  /**
148
197
  * Count the number of entities in a collection
149
198
  */
150
- countEntities?<M extends Record<string, unknown> = Record<string, unknown>>(props: FetchCollectionProps<M>): Promise<number>;
199
+ count?<M extends Record<string, unknown> = Record<string, unknown>>(props: FetchCollectionProps<M>): Promise<number>;
151
200
  /**
152
201
  * Check if the given filter combination is valid
153
202
  * @param props
@@ -165,7 +214,7 @@ export interface DataDriver {
165
214
  context: RebaseContext;
166
215
  path: string;
167
216
  databaseId?: string;
168
- collection: EntityCollection;
217
+ collection: CollectionConfig;
169
218
  parentCollectionSlugs?: string[];
170
219
  parentEntityIds?: string[];
171
220
  }) => Promise<boolean>;
@@ -175,7 +224,7 @@ export interface DataDriver {
175
224
  needsInitTextSearch?: boolean;
176
225
  /**
177
226
  * Optional REST-optimised fetch service. When present, the REST API
178
- * generator uses these methods instead of the generic `fetchEntity` /
227
+ * generator uses these methods instead of the generic `fetchOne` /
179
228
  * `fetchCollection` pipeline, enabling include-aware eager-loading.
180
229
  */
181
230
  restFetchService?: RestFetchService;
@@ -191,8 +240,14 @@ export interface DataDriver {
191
240
  * REST-optimised fetch service exposed by drivers that support
192
241
  * eager-loading of relations via `include`.
193
242
  *
194
- * The methods return flattened rows (`{ id, ...columns }`) rather
195
- * than the `Entity<M>` wrapper used by the generic DataDriver API.
243
+ * The methods return flattened rows exactly the table's columns, under their
244
+ * own names and with the types the database returned — and included relations
245
+ * inlined as plain nested rows. This is the shape served to app developers
246
+ * through the REST API / SDK client.
247
+ *
248
+ * No synthesized `id`: identity is a primary key, which may be named anything
249
+ * and span several columns, so an address is derived by whoever needs one (see
250
+ * `buildCompositeId`) rather than written into the row on top of the data.
196
251
  *
197
252
  * @group DataDriver
198
253
  */
@@ -214,5 +269,5 @@ export interface RestFetchService {
214
269
  /**
215
270
  * Fetch a single flattened entity with optional relation includes.
216
271
  */
217
- fetchEntityForRest(collectionPath: string, entityId: string | number, include?: string[], databaseId?: string): Promise<Record<string, unknown> | null>;
272
+ fetchOneForRest(collectionPath: string, id: string | number, include?: string[], databaseId?: string): Promise<Record<string, unknown> | null>;
218
273
  }