@rebasepro/types 0.11.1-canary.gfd39654 → 0.12.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.
- package/dist/controllers/collection_registry.d.ts +1 -1
- package/dist/controllers/data.d.ts +148 -9
- package/dist/index.es.js +287 -97
- package/dist/index.es.js.map +1 -1
- package/dist/types/admin_block.d.ts +20 -0
- package/dist/types/api_keys.d.ts +60 -5
- package/dist/types/collections.d.ts +56 -31
- package/dist/types/data_source.d.ts +42 -1
- package/dist/types/database_adapter.d.ts +13 -8
- package/dist/types/entity_callbacks.d.ts +2 -2
- package/dist/types/history.d.ts +62 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/postgres_introspection.d.ts +95 -0
- package/dist/types/project_manifest.d.ts +146 -73
- package/dist/types/properties.d.ts +38 -49
- package/dist/types/storage_source.d.ts +60 -0
- package/dist/types/websockets.d.ts +0 -37
- package/package.json +1 -1
- package/src/controllers/collection_registry.ts +1 -1
- package/src/controllers/data.ts +155 -9
- package/src/types/admin_block.ts +48 -0
- package/src/types/api_keys.ts +61 -5
- package/src/types/collections.ts +68 -39
- package/src/types/data_source.ts +60 -1
- package/src/types/database_adapter.ts +13 -8
- package/src/types/entity_callbacks.ts +2 -2
- package/src/types/history.ts +66 -0
- package/src/types/index.ts +2 -0
- package/src/types/postgres_introspection.ts +101 -0
- package/src/types/project_manifest.ts +149 -80
- package/src/types/properties.ts +43 -56
- package/src/types/storage_source.ts +130 -0
- package/src/types/websockets.ts +0 -42
package/src/controllers/data.ts
CHANGED
|
@@ -116,12 +116,12 @@ export interface FindResponse<M extends Record<string, unknown> = Record<string,
|
|
|
116
116
|
|
|
117
117
|
|
|
118
118
|
/**
|
|
119
|
-
* Fluent query builder for the **admin
|
|
119
|
+
* Fluent query builder for the **admin admin** — resolves to `FindResponse<M>`
|
|
120
120
|
* (Snapshot-wrapped rows).
|
|
121
121
|
*
|
|
122
122
|
* @internal App developers should use {@link SDKQueryBuilderInterface}
|
|
123
123
|
* (flat rows, returned by `client.data.*` / `context.data.*`). This
|
|
124
|
-
* Snapshot-flavored variant backs the admin
|
|
124
|
+
* Snapshot-flavored variant backs the admin admin internals only.
|
|
125
125
|
*
|
|
126
126
|
* @group Data
|
|
127
127
|
*/
|
|
@@ -138,13 +138,13 @@ export interface QueryBuilderInterface<M extends Record<string, unknown> = Recor
|
|
|
138
138
|
}
|
|
139
139
|
|
|
140
140
|
/**
|
|
141
|
-
* A single collection's CRUD accessor for the **admin
|
|
141
|
+
* A single collection's CRUD accessor for the **admin admin** — every method
|
|
142
142
|
* resolves to `Snapshot`-wrapped rows (`FindResponse<M>` / `Snapshot<M>`).
|
|
143
143
|
*
|
|
144
144
|
* @internal App developers do **not** use this. The public, symmetric surface
|
|
145
145
|
* is {@link SDKCollectionClient} (flat rows), exposed as `client.data.products`
|
|
146
146
|
* in the SDK and `context.data.products` in framework callbacks. This
|
|
147
|
-
* Snapshot-flavored accessor backs the admin
|
|
147
|
+
* Snapshot-flavored accessor backs the admin admin view-model only.
|
|
148
148
|
*
|
|
149
149
|
* @group Data
|
|
150
150
|
*/
|
|
@@ -246,6 +246,91 @@ export interface FindResult<M extends Record<string, unknown> = Record<string, u
|
|
|
246
246
|
meta: PaginationMeta;
|
|
247
247
|
}
|
|
248
248
|
|
|
249
|
+
/**
|
|
250
|
+
* Which column an iteration seeks on, for keyset ("seek") pagination.
|
|
251
|
+
*
|
|
252
|
+
* Either the column name on its own — sorted ascending — or the column plus an
|
|
253
|
+
* explicit direction. The column must be **unique** and must be the column the
|
|
254
|
+
* query is ordered by; see {@link PageWalkOptions.cursor}.
|
|
255
|
+
*
|
|
256
|
+
* @group Data
|
|
257
|
+
*/
|
|
258
|
+
export type CursorSpec<M extends Record<string, unknown> = Record<string, unknown>> =
|
|
259
|
+
| (Extract<keyof M, string>)
|
|
260
|
+
| { field: Extract<keyof M, string>; direction?: "asc" | "desc" };
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* How {@link SDKCollectionClient.iterate} / {@link SDKCollectionClient.findAll}
|
|
264
|
+
* walk a collection, layered on top of the normal `find()` parameters.
|
|
265
|
+
*
|
|
266
|
+
* @group Data
|
|
267
|
+
*/
|
|
268
|
+
export interface PageWalkOptions<M extends Record<string, unknown> = Record<string, unknown>> {
|
|
269
|
+
/**
|
|
270
|
+
* Rows fetched per request. Defaults to 200; values below 1 are clamped up.
|
|
271
|
+
* This is the request size, not a result cap — the iteration keeps going
|
|
272
|
+
* until the server says there is nothing left.
|
|
273
|
+
*/
|
|
274
|
+
pageSize?: number;
|
|
275
|
+
/**
|
|
276
|
+
* Paginate by **seeking on a column** instead of by offset.
|
|
277
|
+
*
|
|
278
|
+
* Offset paging — the default — re-counts rows on every request, so a row
|
|
279
|
+
* inserted or deleted *while the iteration runs* shifts the window and the
|
|
280
|
+
* walk silently skips or repeats rows. Seeking is immune to that: each page
|
|
281
|
+
* asks for rows strictly after the last one seen, so concurrent writes
|
|
282
|
+
* before the cursor cannot move it.
|
|
283
|
+
*
|
|
284
|
+
* Prefer this whenever the collection has a unique, sortable column
|
|
285
|
+
* (typically its primary key). The column must be unique — a repeated value
|
|
286
|
+
* at a page boundary either skips rows or stalls, and the iterator throws
|
|
287
|
+
* rather than looping — and the query is ordered by it, so a `cursor` and a
|
|
288
|
+
* conflicting `orderBy` is an error, not a silent override.
|
|
289
|
+
*
|
|
290
|
+
* Implemented with the parameters `find()` already takes (an `orderBy` plus
|
|
291
|
+
* a `>` / `<` filter on the cursor column), so it works on every transport
|
|
292
|
+
* and needs nothing new from the server.
|
|
293
|
+
*
|
|
294
|
+
* @example
|
|
295
|
+
* for await (const job of client.data.jobs.iterate({ cursor: "id" })) { … }
|
|
296
|
+
*/
|
|
297
|
+
cursor?: CursorSpec<M>;
|
|
298
|
+
/**
|
|
299
|
+
* Hard ceiling on the number of requests one walk may make, so a server
|
|
300
|
+
* that never stops saying `hasMore` cannot spin forever. Defaults to
|
|
301
|
+
* 10 000 pages; hitting it throws.
|
|
302
|
+
*/
|
|
303
|
+
maxPages?: number;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Parameters accepted by {@link SDKCollectionClient.iterate} — everything
|
|
308
|
+
* `find()` takes except the window itself (`limit`, `offset`, `page`), which
|
|
309
|
+
* the iterator owns, plus the walk options.
|
|
310
|
+
*
|
|
311
|
+
* @group Data
|
|
312
|
+
*/
|
|
313
|
+
export type IterateParams<M extends Record<string, unknown> = Record<string, unknown>> =
|
|
314
|
+
Omit<FindParams<M>, "limit" | "offset" | "page"> & PageWalkOptions<M>;
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Parameters accepted by {@link SDKCollectionClient.findAll}: the iteration
|
|
318
|
+
* parameters plus the ceiling that keeps a whole collection from being pulled
|
|
319
|
+
* into memory unnoticed.
|
|
320
|
+
*
|
|
321
|
+
* @group Data
|
|
322
|
+
*/
|
|
323
|
+
export type FindAllParams<M extends Record<string, unknown> = Record<string, unknown>> =
|
|
324
|
+
IterateParams<M> & {
|
|
325
|
+
/**
|
|
326
|
+
* Most rows to materialise. Defaults to 10 000. Exceeding it **throws**
|
|
327
|
+
* — a truncated array returned as if it were the whole answer is the
|
|
328
|
+
* kind of quiet wrong that shows up months later in a report. Pass
|
|
329
|
+
* `Infinity` to opt out deliberately, or use `iterate()` to stream.
|
|
330
|
+
*/
|
|
331
|
+
maxRows?: number;
|
|
332
|
+
};
|
|
333
|
+
|
|
249
334
|
/**
|
|
250
335
|
* Fluent Query Builder Interface for the SDK client.
|
|
251
336
|
* Returns `FindResult<M>` (flat rows) instead of `FindResponse<M>` (Entity-wrapped).
|
|
@@ -269,7 +354,7 @@ export interface SDKQueryBuilderInterface<M extends Record<string, unknown> = Re
|
|
|
269
354
|
* SDK collection client — returns flat rows, no Entity wrapper.
|
|
270
355
|
*
|
|
271
356
|
* This is the public API surface for app developers using
|
|
272
|
-
* `createRebaseClient()`.
|
|
357
|
+
* `createRebaseClient()`. admin internals use `CollectionAccessor` instead.
|
|
273
358
|
*
|
|
274
359
|
* Type parameters:
|
|
275
360
|
* - `M` — the **Row** shape returned by reads (`find`, `findById`, `listen`).
|
|
@@ -321,6 +406,67 @@ export interface SDKCollectionClient<
|
|
|
321
406
|
*/
|
|
322
407
|
find(params?: FindParams<M>): Promise<FindResult<M>>;
|
|
323
408
|
|
|
409
|
+
/**
|
|
410
|
+
* Walk every record matching a query, one row at a time, fetching pages as
|
|
411
|
+
* the consumer consumes them.
|
|
412
|
+
*
|
|
413
|
+
* This is the pagination primitive: `find()` returns one window, `iterate()`
|
|
414
|
+
* returns all of them without the caller hand-rolling the
|
|
415
|
+
* `limit` / `offset += ` / "am I done yet" loop. Nothing is buffered — rows
|
|
416
|
+
* are yielded as each page arrives, so a million-row walk costs one page of
|
|
417
|
+
* memory. `break` stops the walk and no further requests are made.
|
|
418
|
+
*
|
|
419
|
+
* Termination is driven by the server's `meta.hasMore`, never by comparing
|
|
420
|
+
* a page's length against the requested limit — a final page that happens
|
|
421
|
+
* to be exactly full is indistinguishable that way, and a walk that stops
|
|
422
|
+
* there drops rows. An empty page also ends the walk, and
|
|
423
|
+
* {@link PageWalkOptions.maxPages} bounds a server that never stops saying
|
|
424
|
+
* there is more.
|
|
425
|
+
*
|
|
426
|
+
* ## Consistency
|
|
427
|
+
*
|
|
428
|
+
* By default this pages by **offset**, which is only as stable as the table
|
|
429
|
+
* is still: a row inserted or deleted ahead of the cursor between two
|
|
430
|
+
* requests shifts every later window, so the walk can skip a row or hand
|
|
431
|
+
* back the same one twice. That is inherent to offset paging, not a bug
|
|
432
|
+
* here. On a collection with a unique sortable column, pass
|
|
433
|
+
* {@link PageWalkOptions.cursor} to seek on it instead — the walk then
|
|
434
|
+
* asks for rows strictly after the last one it saw, which concurrent writes
|
|
435
|
+
* cannot perturb.
|
|
436
|
+
*
|
|
437
|
+
* @example
|
|
438
|
+
* for await (const job of client.data.jobs.iterate({
|
|
439
|
+
* where: { status: ["==", "queued"] },
|
|
440
|
+
* cursor: "id",
|
|
441
|
+
* pageSize: 500
|
|
442
|
+
* })) {
|
|
443
|
+
* await handle(job);
|
|
444
|
+
* }
|
|
445
|
+
*/
|
|
446
|
+
iterate(params?: IterateParams<M>): AsyncIterableIterator<M>;
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* {@link iterate}, collected into an array.
|
|
450
|
+
*
|
|
451
|
+
* Convenient when the result is known to be small and awkward to stream.
|
|
452
|
+
* Because "known to be small" is an assumption and not a fact, the result is
|
|
453
|
+
* capped — 10 000 rows by default — and going over the cap **throws**
|
|
454
|
+
* rather than returning a short array that reads like a complete one. Raise
|
|
455
|
+
* {@link FindAllParams.maxRows} when the data really is bigger, or switch to
|
|
456
|
+
* `iterate()` and stream it.
|
|
457
|
+
*
|
|
458
|
+
* The offset-drift caveat on {@link iterate} applies here too.
|
|
459
|
+
*
|
|
460
|
+
* @throws When more rows match than `maxRows` allows.
|
|
461
|
+
*
|
|
462
|
+
* @example
|
|
463
|
+
* const overdue = await client.data.invoices.findAll({
|
|
464
|
+
* where: { due_at: ["<", today] },
|
|
465
|
+
* cursor: "id"
|
|
466
|
+
* });
|
|
467
|
+
*/
|
|
468
|
+
findAll(params?: FindAllParams<M>): Promise<M[]>;
|
|
469
|
+
|
|
324
470
|
/**
|
|
325
471
|
* Find a single record by its ID.
|
|
326
472
|
*/
|
|
@@ -406,16 +552,16 @@ export interface SDKCollectionClient<
|
|
|
406
552
|
}
|
|
407
553
|
|
|
408
554
|
/**
|
|
409
|
-
* The unified data access object for the **admin
|
|
555
|
+
* The unified data access object for the **admin admin** (Entity-shaped).
|
|
410
556
|
*
|
|
411
557
|
* Access collections as dynamic properties: `data.products.find(...)`. Each
|
|
412
558
|
* accessor returns `Entity`-wrapped records (`{ id, path, values }`) — the
|
|
413
|
-
* view-model the
|
|
559
|
+
* view-model the admin renders. This is what `useData()` / the admin
|
|
414
560
|
* `RebaseContext.data` are backed by.
|
|
415
561
|
*
|
|
416
562
|
* @internal App developers do **not** use this — they use
|
|
417
563
|
* {@link RebaseSdkData} (flat rows), which is what the SDK client and backend
|
|
418
|
-
* `context.data` expose. This Entity-shaped map backs the admin
|
|
564
|
+
* `context.data` expose. This Entity-shaped map backs the admin admin only.
|
|
419
565
|
*
|
|
420
566
|
* @group Data
|
|
421
567
|
*/
|
|
@@ -455,7 +601,7 @@ export type RebaseData<DB = unknown> = {
|
|
|
455
601
|
*
|
|
456
602
|
* Every accessor returns flat rows (the table's columns) via
|
|
457
603
|
* {@link SDKCollectionClient} — access fields directly (`row.title`), never
|
|
458
|
-
* `row.values.title`. The admin
|
|
604
|
+
* `row.values.title`. The admin uses {@link RebaseData} (Entity) instead.
|
|
459
605
|
*
|
|
460
606
|
* @example
|
|
461
607
|
* // Frontend SDK
|
package/src/types/admin_block.ts
CHANGED
|
@@ -73,3 +73,51 @@ export const ADMIN_COLLECTION_KEYS = [
|
|
|
73
73
|
|
|
74
74
|
/** A key of a collection's `admin` block. @group Models */
|
|
75
75
|
export type AdminCollectionKey = typeof ADMIN_COLLECTION_KEYS[number];
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Every key that belongs inside a *property's* `admin` block, as data.
|
|
79
|
+
*
|
|
80
|
+
* The union of `AdminPropertyOptions` and its per-type extensions
|
|
81
|
+
* (`AdminStringOptions`, `AdminArrayOptions`, …) in `@rebasepro/admin-types`.
|
|
82
|
+
* It lives here for the same reason {@link ADMIN_COLLECTION_KEYS} does: the
|
|
83
|
+
* runtime consumers are core packages that the BaaS guard forbids from
|
|
84
|
+
* importing `@rebasepro/admin-types`. Here it is the boot-time collection
|
|
85
|
+
* validator in `@rebasepro/server`, which has to tell "you left `readOnly` at
|
|
86
|
+
* the top of the property, where nothing reads it" apart from "you invented a
|
|
87
|
+
* key we have never heard of".
|
|
88
|
+
*
|
|
89
|
+
* `@rebasepro/admin-types` re-exports this and asserts it names only real
|
|
90
|
+
* option keys.
|
|
91
|
+
*
|
|
92
|
+
* @group Models
|
|
93
|
+
*/
|
|
94
|
+
export const ADMIN_PROPERTY_KEYS = [
|
|
95
|
+
"canAddElements",
|
|
96
|
+
"clearable",
|
|
97
|
+
"columnWidth",
|
|
98
|
+
"customProps",
|
|
99
|
+
"disabled",
|
|
100
|
+
"expanded",
|
|
101
|
+
"Field",
|
|
102
|
+
"Filter",
|
|
103
|
+
"filterOperators",
|
|
104
|
+
"fixedFilter",
|
|
105
|
+
"hideFromCollection",
|
|
106
|
+
"includeEntityLink",
|
|
107
|
+
"includeId",
|
|
108
|
+
"markdown",
|
|
109
|
+
"minimalistView",
|
|
110
|
+
"multiline",
|
|
111
|
+
"Preview",
|
|
112
|
+
"previewAsTag",
|
|
113
|
+
"previewProperties",
|
|
114
|
+
"readOnly",
|
|
115
|
+
"sortable",
|
|
116
|
+
"spreadChildren",
|
|
117
|
+
"urlPreview",
|
|
118
|
+
"widget",
|
|
119
|
+
"widthPercentage"
|
|
120
|
+
] as const;
|
|
121
|
+
|
|
122
|
+
/** A key of a property's `admin` block. @group Models */
|
|
123
|
+
export type AdminPropertyKey = typeof ADMIN_PROPERTY_KEYS[number];
|
package/src/types/api_keys.ts
CHANGED
|
@@ -1,16 +1,56 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
* Service API keys — machine-to-machine authentication for scripts, cron jobs
|
|
3
|
+
* and third-party integrations.
|
|
4
|
+
*
|
|
5
|
+
* The wire contract lives here because all three sides need it and used to
|
|
6
|
+
* declare it separately: `@rebasepro/server` implements the routes,
|
|
7
|
+
* `@rebasepro/client` calls them, and {@link ApiKeysAPI} types the SDK surface.
|
|
8
|
+
* The client's copy had already drifted — it never gained `admin`.
|
|
9
|
+
*
|
|
10
|
+
* The database row itself (`ApiKey`, which carries `key_hash`) stays in the
|
|
11
|
+
* server package: nothing off the server may see it.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* A single permission entry scoping an API key to a collection and set of
|
|
16
|
+
* operations.
|
|
17
|
+
*
|
|
18
|
+
* Use `"*"` as the collection value to grant access to all collections (and all
|
|
19
|
+
* custom functions). Custom functions are addressed with the `functions`
|
|
20
|
+
* namespace: `"functions"` grants every function, `"functions/<name>"` grants a
|
|
21
|
+
* single one.
|
|
22
|
+
*
|
|
23
|
+
* @group Models
|
|
24
|
+
*/
|
|
2
25
|
export interface ApiKeyPermission {
|
|
26
|
+
/** Collection slug, `"functions"`/`"functions/<name>"`, or `"*"` for everything. */
|
|
3
27
|
collection: string;
|
|
28
|
+
/** Allowed operations on the collection. */
|
|
4
29
|
operations: ("read" | "write" | "delete")[];
|
|
5
30
|
}
|
|
6
31
|
|
|
7
|
-
/**
|
|
32
|
+
/**
|
|
33
|
+
* An API key with the secret portion masked — what list / get / update return.
|
|
34
|
+
* @group Models
|
|
35
|
+
*/
|
|
8
36
|
export interface ApiKeyMasked {
|
|
9
37
|
id: string;
|
|
10
38
|
name: string;
|
|
39
|
+
/** First 12 characters of the plaintext key, for display only. */
|
|
11
40
|
key_prefix: string;
|
|
12
41
|
permissions: ApiKeyPermission[];
|
|
42
|
+
/**
|
|
43
|
+
* When true, the key is granted the `admin` role: it passes the admin-gated
|
|
44
|
+
* routes (users, roles, cron, backups, logs, API keys) and the RLS
|
|
45
|
+
* `default_admin` policies. Non-admin keys carry only the `service` role —
|
|
46
|
+
* RLS grants them nothing unless a collection policy names that role.
|
|
47
|
+
*/
|
|
13
48
|
admin: boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Requests per 15-minute window. `null` means "no per-key override" — the
|
|
51
|
+
* data rate limiter then applies its default API-key limit (1000/window
|
|
52
|
+
* unless configured otherwise), not unlimited.
|
|
53
|
+
*/
|
|
14
54
|
rate_limit: number | null;
|
|
15
55
|
created_by: string;
|
|
16
56
|
created_at: string;
|
|
@@ -20,29 +60,45 @@ export interface ApiKeyMasked {
|
|
|
20
60
|
revoked_at: string | null;
|
|
21
61
|
}
|
|
22
62
|
|
|
23
|
-
/**
|
|
63
|
+
/**
|
|
64
|
+
* Returned exactly once, when a key is created. The `key` field holds the full
|
|
65
|
+
* plaintext key — it is never stored or returned again.
|
|
66
|
+
* @group Models
|
|
67
|
+
*/
|
|
24
68
|
export interface ApiKeyWithSecret extends ApiKeyMasked {
|
|
69
|
+
/** Full plaintext API key (e.g. `rk_live_abc123...`). */
|
|
25
70
|
key: string;
|
|
26
71
|
}
|
|
27
72
|
|
|
28
|
-
/**
|
|
73
|
+
/**
|
|
74
|
+
* Payload for creating a new API key.
|
|
75
|
+
* @group Models
|
|
76
|
+
*/
|
|
29
77
|
export interface CreateApiKeyRequest {
|
|
30
78
|
name: string;
|
|
31
79
|
permissions: ApiKeyPermission[];
|
|
80
|
+
/** When true, grants the `admin` role. See {@link ApiKeyMasked.admin}. */
|
|
32
81
|
admin?: boolean;
|
|
82
|
+
/** Requests per 15-minute window. Omit or `null` for the server default. */
|
|
33
83
|
rate_limit?: number | null;
|
|
84
|
+
/** ISO-8601 expiration timestamp. Omit for no expiration. */
|
|
34
85
|
expires_at?: string | null;
|
|
35
86
|
}
|
|
36
87
|
|
|
37
|
-
/**
|
|
88
|
+
/**
|
|
89
|
+
* Payload for updating an existing API key. Only the fields provided change.
|
|
90
|
+
* @group Models
|
|
91
|
+
*/
|
|
38
92
|
export interface UpdateApiKeyRequest {
|
|
39
93
|
name?: string;
|
|
40
94
|
permissions?: ApiKeyPermission[];
|
|
95
|
+
/** When true, grants the `admin` role. See {@link ApiKeyMasked.admin}. */
|
|
41
96
|
admin?: boolean;
|
|
42
97
|
rate_limit?: number | null;
|
|
43
98
|
expires_at?: string | null;
|
|
44
99
|
}
|
|
45
100
|
|
|
101
|
+
/** @group Models */
|
|
46
102
|
export interface ApiKeysAPI {
|
|
47
103
|
listKeys(): Promise<{ keys: ApiKeyMasked[] }>;
|
|
48
104
|
getKey(id: string): Promise<{ key: ApiKeyMasked }>;
|
package/src/types/collections.ts
CHANGED
|
@@ -5,6 +5,7 @@ import type { EnumValues, Properties, PostgresProperties, FirebaseProperties, Mo
|
|
|
5
5
|
import type { User } from "../users";
|
|
6
6
|
import type { Relation } from "./relations";
|
|
7
7
|
import type { SecurityRule } from "./security_rules";
|
|
8
|
+
import { getDataSourceCapabilities } from "./data_source";
|
|
8
9
|
import type { WhereFilterOp, FilterValues, FilterPreset } from "./filter-operators";
|
|
9
10
|
|
|
10
11
|
/**
|
|
@@ -116,30 +117,34 @@ export interface BaseCollectionConfig<M extends Record<string, unknown> = Record
|
|
|
116
117
|
*/
|
|
117
118
|
auth?: boolean | AuthCollectionConfig;
|
|
118
119
|
|
|
119
|
-
/**
|
|
120
|
-
* Opt out of the framework's default Row Level Security policies.
|
|
121
|
-
*
|
|
122
|
-
* The schema generator automatically injects, for every collection, a
|
|
123
|
-
* baseline SELECT policy granting the trusted server context and the
|
|
124
|
-
* `admin` role read access (reads run under a restricted role, so RLS
|
|
125
|
-
* default-denies without it). For auth collections it additionally injects
|
|
126
|
-
* a self-read policy (`id = auth.uid()`) and an admin-only write gate
|
|
127
|
-
* (INSERT/UPDATE/DELETE require the `admin` role or the trusted server
|
|
128
|
-
* context), making privileged columns such as `roles` safe by default.
|
|
129
|
-
*
|
|
130
|
-
* Author-defined `securityRules` are permissive and broaden access on top
|
|
131
|
-
* of these defaults. Set this flag to `true` to remove the defaults
|
|
132
|
-
* entirely and take full responsibility for the collection's RLS.
|
|
133
|
-
*
|
|
134
|
-
* @default false
|
|
135
|
-
*/
|
|
136
|
-
disableDefaultPolicies?: boolean;
|
|
137
120
|
|
|
138
121
|
|
|
139
122
|
|
|
140
123
|
|
|
141
124
|
|
|
142
125
|
|
|
126
|
+
/**
|
|
127
|
+
* Row-level authorization rules for this collection.
|
|
128
|
+
*
|
|
129
|
+
* Driver-agnostic on purpose, unlike `disableDefaultPolicies`, `table` and
|
|
130
|
+
* `relations`, which are declared on {@link PostgresCollectionConfig} only.
|
|
131
|
+
* The rules are a *contract* — who may read or write which rows — and each
|
|
132
|
+
* engine enforces it its own way:
|
|
133
|
+
*
|
|
134
|
+
* - **Postgres** compiles them to real `CREATE POLICY` statements and lets
|
|
135
|
+
* the database enforce them (see {@link PostgresCollectionConfig.securityRules},
|
|
136
|
+
* which narrows this with the raw-SQL details).
|
|
137
|
+
* - **MongoDB** translates them into a query filter it AND-s into every
|
|
138
|
+
* read and write, honouring `access`, `ownerField`, `roles`, `mode` and
|
|
139
|
+
* the `operation`/`operations` selectors, and making a best effort at raw
|
|
140
|
+
* `using`/`withCheck` SQL.
|
|
141
|
+
* - **Firestore** does not implement them at all; its own rules language is
|
|
142
|
+
* evaluated by Google, not from here. `supportsRLS` on
|
|
143
|
+
* {@link DataSourceCapabilities} reports which engines generate policies,
|
|
144
|
+
* which is not the same question as whether an engine honours a rule.
|
|
145
|
+
*/
|
|
146
|
+
securityRules?: readonly SecurityRule[];
|
|
147
|
+
|
|
143
148
|
/**
|
|
144
149
|
* This interface defines all the callbacks that can be used when a entity
|
|
145
150
|
* is being created, updated or deleted.
|
|
@@ -202,26 +207,6 @@ export interface BaseCollectionConfig<M extends Record<string, unknown> = Record
|
|
|
202
207
|
|
|
203
208
|
|
|
204
209
|
|
|
205
|
-
/**
|
|
206
|
-
* The database table name for this collection.
|
|
207
|
-
* Automatically set for PostgreSQL collections.
|
|
208
|
-
* For non-SQL backends, this may be undefined.
|
|
209
|
-
*/
|
|
210
|
-
table?: string;
|
|
211
|
-
|
|
212
|
-
/**
|
|
213
|
-
* Relations defined for this collection.
|
|
214
|
-
* Populated at normalization time from inline relation properties
|
|
215
|
-
* or explicit relation definitions.
|
|
216
|
-
*/
|
|
217
|
-
relations?: Relation[];
|
|
218
|
-
|
|
219
|
-
/**
|
|
220
|
-
* Security rules for this collection (Row Level Security).
|
|
221
|
-
* When defined, the backend enforces access control policies.
|
|
222
|
-
*/
|
|
223
|
-
securityRules?: readonly SecurityRule[];
|
|
224
|
-
|
|
225
210
|
}
|
|
226
211
|
|
|
227
212
|
// ── Driver-specific collection types ──────────────────────────────────
|
|
@@ -279,6 +264,25 @@ export interface PostgresCollectionConfig<M extends Record<string, unknown> = Re
|
|
|
279
264
|
* - `auth.jwt()` — full JWT claims as JSONB
|
|
280
265
|
*/
|
|
281
266
|
securityRules?: readonly SecurityRule[];
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Opt out of the framework's default Row Level Security policies.
|
|
270
|
+
*
|
|
271
|
+
* The schema generator automatically injects, for every collection, a
|
|
272
|
+
* baseline SELECT policy granting the trusted server context and the
|
|
273
|
+
* `admin` role read access (reads run under a restricted role, so RLS
|
|
274
|
+
* default-denies without it). For auth collections it additionally injects
|
|
275
|
+
* a self-read policy (`id = auth.uid()`) and an admin-only write gate
|
|
276
|
+
* (INSERT/UPDATE/DELETE require the `admin` role or the trusted server
|
|
277
|
+
* context), making privileged columns such as `roles` safe by default.
|
|
278
|
+
*
|
|
279
|
+
* Author-defined `securityRules` are permissive and broaden access on top
|
|
280
|
+
* of these defaults. Set this flag to `true` to remove the defaults
|
|
281
|
+
* entirely and take full responsibility for the collection's RLS.
|
|
282
|
+
*
|
|
283
|
+
* @default false
|
|
284
|
+
*/
|
|
285
|
+
disableDefaultPolicies?: boolean;
|
|
282
286
|
}
|
|
283
287
|
|
|
284
288
|
/**
|
|
@@ -404,7 +408,6 @@ export type CollectionConfig<M extends Record<string, unknown> = Record<string,
|
|
|
404
408
|
*
|
|
405
409
|
* @group Models
|
|
406
410
|
*/
|
|
407
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
408
411
|
export type AnyCollectionConfig = CollectionConfig<any, any>;
|
|
409
412
|
|
|
410
413
|
/**
|
|
@@ -425,6 +428,32 @@ export function isPostgresCollectionConfig<C extends CollectionConfig<any, any>>
|
|
|
425
428
|
return !collection.engine || collection.engine === "postgres";
|
|
426
429
|
}
|
|
427
430
|
|
|
431
|
+
/**
|
|
432
|
+
* Narrows to the SQL collection fields — `table`, `relations`,
|
|
433
|
+
* `disableDefaultPolicies` — by asking the engine's declared capabilities
|
|
434
|
+
* rather than by naming Postgres.
|
|
435
|
+
*
|
|
436
|
+
* The two halves of this already existed and were never joined. The engine
|
|
437
|
+
* split (`PostgresCollectionConfig` / `FirebaseCollectionConfig` /
|
|
438
|
+
* `MongoDBCollectionConfig`) said which fields belong to which engine at the
|
|
439
|
+
* type level; {@link DataSourceCapabilities} said the same thing at runtime,
|
|
440
|
+
* down to a `supportsRelations` flag. So call sites guarded on the capability
|
|
441
|
+
* and then read a field the base type had to declare for them — which is why
|
|
442
|
+
* those fields were on the base, and why a MongoDB collection could be written
|
|
443
|
+
* with a `table`.
|
|
444
|
+
*
|
|
445
|
+
* Prefer this over {@link isPostgresCollectionConfig} wherever the question is
|
|
446
|
+
* "does this collection live in a SQL table", so a custom SQL engine
|
|
447
|
+
* registered through `registerDataSourceCapabilities` is included.
|
|
448
|
+
*
|
|
449
|
+
* @group Models
|
|
450
|
+
*/
|
|
451
|
+
export function isRelationalCollectionConfig<C extends CollectionConfig<any, any>>(
|
|
452
|
+
collection: C
|
|
453
|
+
): collection is C & PostgresCollectionConfig<any, any> {
|
|
454
|
+
return getDataSourceCapabilities(collection.engine).supportsRelations;
|
|
455
|
+
}
|
|
456
|
+
|
|
428
457
|
/**
|
|
429
458
|
* Type guard for Firebase / Firestore collections.
|
|
430
459
|
* @group Models
|
package/src/types/data_source.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { ALL_WHERE_FILTER_OPS, WhereFilterOp } from "./filter-operators";
|
|
|
4
4
|
* Describes the capabilities and features supported by a data source (driver).
|
|
5
5
|
*
|
|
6
6
|
* Each driver (Postgres, Firebase, MongoDB, etc.) declares which features it
|
|
7
|
-
* supports. The
|
|
7
|
+
* supports. The admin uses this descriptor to:
|
|
8
8
|
* - Show/hide editor tabs (e.g. Relations for SQL, Subcollections for Firebase)
|
|
9
9
|
* - Filter the property type picker (e.g. `relation` for SQL, `reference` for Firebase)
|
|
10
10
|
* - Toggle driver-specific form controls (e.g. `columnType` for SQL)
|
|
@@ -37,6 +37,17 @@ export interface DataSourceCapabilities {
|
|
|
37
37
|
/** Does this source support real-time listeners? */
|
|
38
38
|
supportsRealtime: boolean;
|
|
39
39
|
|
|
40
|
+
/**
|
|
41
|
+
* Does this source store vectors natively?
|
|
42
|
+
*
|
|
43
|
+
* `VectorProperty` carries a `dimensions` and is pgvector-shaped. It was
|
|
44
|
+
* the one driver-specific property kind with no flag to gate it, so unlike
|
|
45
|
+
* every other field in this descriptor there was not even a runtime answer
|
|
46
|
+
* to appeal to — a Firestore collection could declare an embedding column
|
|
47
|
+
* and no driver would do anything with it.
|
|
48
|
+
*/
|
|
49
|
+
supportsVectors: boolean;
|
|
50
|
+
|
|
40
51
|
/**
|
|
41
52
|
* Canonical filter operators this engine can execute.
|
|
42
53
|
*
|
|
@@ -48,6 +59,28 @@ export interface DataSourceCapabilities {
|
|
|
48
59
|
*/
|
|
49
60
|
filterOperators: readonly WhereFilterOp[];
|
|
50
61
|
|
|
62
|
+
/**
|
|
63
|
+
* Relation kinds this engine's driver can compile into a filter.
|
|
64
|
+
*
|
|
65
|
+
* Only `belongsTo` puts a column on the row being filtered; the others are
|
|
66
|
+
* answered with a correlated subquery over the junction or the target
|
|
67
|
+
* table, which not every driver can build. An engine with no relations at
|
|
68
|
+
* all declares none.
|
|
69
|
+
*
|
|
70
|
+
* The admin uses this to decide whether a relation column offers a filter
|
|
71
|
+
* control. Offering one an engine cannot answer is not cosmetic: a driver
|
|
72
|
+
* that drops the key it cannot resolve *widens* the read to every row, and
|
|
73
|
+
* one that fails closed answers a control the admin itself put on screen
|
|
74
|
+
* with a 400.
|
|
75
|
+
*
|
|
76
|
+
* Optional, so a third-party driver registered before this existed still
|
|
77
|
+
* compiles. Omitted means {@link DEFAULT_FILTERABLE_RELATION_KINDS} — the
|
|
78
|
+
* one kind that is a plain column comparison, which every relational
|
|
79
|
+
* driver can do. The subquery kinds are a real capability and have to be
|
|
80
|
+
* claimed rather than assumed: assuming them wrongly is the widening.
|
|
81
|
+
*/
|
|
82
|
+
filterableRelationKinds?: readonly string[];
|
|
83
|
+
|
|
51
84
|
// ── Admin capability flags ───────────────────────────────────────
|
|
52
85
|
/** Does this source support SQL admin operations (SQL editor, EXPLAIN, etc.)? */
|
|
53
86
|
supportsSQLAdmin: boolean;
|
|
@@ -152,6 +185,17 @@ export interface ResolvedDataSource {
|
|
|
152
185
|
capabilities: DataSourceCapabilities;
|
|
153
186
|
}
|
|
154
187
|
|
|
188
|
+
/**
|
|
189
|
+
* Relation kinds assumed filterable when a driver does not say.
|
|
190
|
+
*
|
|
191
|
+
* `belongsTo` alone: its filter is a comparison on a column of the row being
|
|
192
|
+
* filtered, the one shape that needs no query construction a driver might not
|
|
193
|
+
* have. Everything else is a correlated subquery over another table.
|
|
194
|
+
*
|
|
195
|
+
* @group Models
|
|
196
|
+
*/
|
|
197
|
+
export const DEFAULT_FILTERABLE_RELATION_KINDS: readonly string[] = ["belongsTo"];
|
|
198
|
+
|
|
155
199
|
// ── Built-in driver capabilities ─────────────────────────────────────
|
|
156
200
|
|
|
157
201
|
/** @group Models */
|
|
@@ -164,7 +208,11 @@ export const POSTGRES_CAPABILITIES: DataSourceCapabilities = {
|
|
|
164
208
|
supportsReferences: false,
|
|
165
209
|
supportsColumnTypes: true,
|
|
166
210
|
supportsRealtime: true,
|
|
211
|
+
supportsVectors: true,
|
|
167
212
|
filterOperators: ALL_WHERE_FILTER_OPS,
|
|
213
|
+
// `via` is absent: its join path is authored source → target with no
|
|
214
|
+
// stated inverse, so the driver has nothing to reverse into a filter.
|
|
215
|
+
filterableRelationKinds: ["belongsTo", "manyToMany", "hasMany", "hasOne"],
|
|
168
216
|
supportsSQLAdmin: true,
|
|
169
217
|
supportsDocumentAdmin: false,
|
|
170
218
|
supportsSchemaAdmin: true
|
|
@@ -180,10 +228,13 @@ export const FIREBASE_CAPABILITIES: DataSourceCapabilities = {
|
|
|
180
228
|
supportsReferences: true,
|
|
181
229
|
supportsColumnTypes: false,
|
|
182
230
|
supportsRealtime: true,
|
|
231
|
+
supportsVectors: false,
|
|
183
232
|
// Firestore has no SQL pattern matching — the driver throws on the LIKE
|
|
184
233
|
// family, so the UI must never offer it.
|
|
185
234
|
filterOperators: ALL_WHERE_FILTER_OPS.filter(op =>
|
|
186
235
|
op !== "like" && op !== "ilike" && op !== "not-like" && op !== "not-ilike"),
|
|
236
|
+
// No relations at all — a document store links by reference.
|
|
237
|
+
filterableRelationKinds: [],
|
|
187
238
|
supportsSQLAdmin: false,
|
|
188
239
|
supportsDocumentAdmin: false,
|
|
189
240
|
supportsSchemaAdmin: false
|
|
@@ -199,7 +250,9 @@ export const MONGODB_CAPABILITIES: DataSourceCapabilities = {
|
|
|
199
250
|
supportsReferences: true,
|
|
200
251
|
supportsColumnTypes: false,
|
|
201
252
|
supportsRealtime: false,
|
|
253
|
+
supportsVectors: false,
|
|
202
254
|
filterOperators: ALL_WHERE_FILTER_OPS,
|
|
255
|
+
filterableRelationKinds: [],
|
|
203
256
|
supportsSQLAdmin: false,
|
|
204
257
|
supportsDocumentAdmin: true,
|
|
205
258
|
supportsSchemaAdmin: true
|
|
@@ -219,7 +272,13 @@ export const DEFAULT_CAPABILITIES: DataSourceCapabilities = {
|
|
|
219
272
|
supportsReferences: true,
|
|
220
273
|
supportsColumnTypes: true,
|
|
221
274
|
supportsRealtime: true,
|
|
275
|
+
supportsVectors: true,
|
|
222
276
|
filterOperators: ALL_WHERE_FILTER_OPS,
|
|
277
|
+
// The exception to this descriptor's "enable everything" rule. The other
|
|
278
|
+
// flags hide a tab or a picker when they are wrong; this one decides
|
|
279
|
+
// whether a query is sent that an unknown driver may answer by dropping
|
|
280
|
+
// the condition — which returns every row rather than none.
|
|
281
|
+
filterableRelationKinds: DEFAULT_FILTERABLE_RELATION_KINDS,
|
|
223
282
|
supportsSQLAdmin: true,
|
|
224
283
|
supportsDocumentAdmin: true,
|
|
225
284
|
supportsSchemaAdmin: true
|