@rebasepro/types 0.16.1-canary.gef08a6e → 0.17.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/README.md +1 -1
- package/dist/index.es.js +7 -7
- package/dist/index.es.js.map +1 -1
- package/dist/types/admin_block.d.ts +7 -7
- package/dist/types/collections.d.ts +19 -1
- package/dist/types/component_ref.d.ts +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/indexes.d.ts +179 -0
- package/dist/types/postgres_introspection.d.ts +1 -1
- package/dist/types/properties.d.ts +1 -1
- package/package.json +1 -1
- package/src/types/admin_block.ts +7 -7
- package/src/types/collections.ts +20 -1
- package/src/types/component_ref.ts +1 -1
- package/src/types/index.ts +1 -0
- package/src/types/indexes.ts +180 -0
- package/src/types/postgres_introspection.ts +1 -1
- package/src/types/properties.ts +1 -1
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* There is no *type* for the block in this package any more, and that is the point:
|
|
5
5
|
* `admin` is not declared on `BaseCollectionConfig` or on any property here, so a
|
|
6
|
-
* BaaS install cannot even write one. `@rebasepro/
|
|
6
|
+
* BaaS install cannot even write one. `@rebasepro/cms-types` adds the field back by
|
|
7
7
|
* declaration merging, which is why installing it is what makes the admin surface
|
|
8
8
|
* appear.
|
|
9
9
|
*
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* Every key that belongs inside a collection's `admin` block, as data.
|
|
15
15
|
*
|
|
16
16
|
* The type that describes these fields is `AdminCollectionOptions` in
|
|
17
|
-
* `@rebasepro/
|
|
17
|
+
* `@rebasepro/cms-types`, and it is erased at build time — but three runtime
|
|
18
18
|
* consumers need the list, and two of them are core:
|
|
19
19
|
*
|
|
20
20
|
* - `serializeCollections`, to drop the block from the contract
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
* backend ignores it and the panel never finds it again.
|
|
25
25
|
* - the `collections-admin-block` codemod
|
|
26
26
|
*
|
|
27
|
-
* `@rebasepro/
|
|
27
|
+
* `@rebasepro/cms-types` re-exports this and asserts it names only real option
|
|
28
28
|
* keys; the count is pinned by a test there.
|
|
29
29
|
*
|
|
30
30
|
* @group Models
|
|
@@ -36,15 +36,15 @@ export type AdminCollectionKey = typeof ADMIN_COLLECTION_KEYS[number];
|
|
|
36
36
|
* Every key that belongs inside a *property's* `admin` block, as data.
|
|
37
37
|
*
|
|
38
38
|
* The union of `AdminPropertyOptions` and its per-type extensions
|
|
39
|
-
* (`AdminStringOptions`, `AdminArrayOptions`, …) in `@rebasepro/
|
|
39
|
+
* (`AdminStringOptions`, `AdminArrayOptions`, …) in `@rebasepro/cms-types`.
|
|
40
40
|
* It lives here for the same reason {@link ADMIN_COLLECTION_KEYS} does: the
|
|
41
41
|
* runtime consumers are core packages that the BaaS guard forbids from
|
|
42
|
-
* importing `@rebasepro/
|
|
42
|
+
* importing `@rebasepro/cms-types`. Here it is the boot-time collection
|
|
43
43
|
* validator in `@rebasepro/server`, which has to tell "you left `readOnly` at
|
|
44
44
|
* the top of the property, where nothing reads it" apart from "you invented a
|
|
45
45
|
* key we have never heard of".
|
|
46
46
|
*
|
|
47
|
-
* `@rebasepro/
|
|
47
|
+
* `@rebasepro/cms-types` re-exports this and asserts it names only real
|
|
48
48
|
* option keys.
|
|
49
49
|
*
|
|
50
50
|
* @group Models
|
|
@@ -65,7 +65,7 @@ export type AdminPropertyKey = typeof ADMIN_PROPERTY_KEYS[number];
|
|
|
65
65
|
* in favour of the value the user changed away from.
|
|
66
66
|
*
|
|
67
67
|
* This lives here, next to the key lists, because it had two implementations —
|
|
68
|
-
* `toAdminCollectionConfig` in `@rebasepro/
|
|
68
|
+
* `toAdminCollectionConfig` in `@rebasepro/cms-types` and `nestAdminKeys` in
|
|
69
69
|
* `@rebasepro/server`'s schema editor — that agreed on everything except that
|
|
70
70
|
* precedence, which is the only part that decides whether a save is visible.
|
|
71
71
|
*
|
|
@@ -5,6 +5,7 @@ import type { EmailSendResult } from "../controllers/email.js";
|
|
|
5
5
|
import type { Relation } from "./relations.js";
|
|
6
6
|
import type { SecurityRule } from "./security_rules.js";
|
|
7
7
|
import type { SearchConfig } from "./search.js";
|
|
8
|
+
import type { CollectionIndex } from "./indexes.js";
|
|
8
9
|
/**
|
|
9
10
|
* Base interface containing all driver-agnostic collection properties.
|
|
10
11
|
* Use {@link PostgresCollectionConfig} or {@link FirebaseCollectionConfig} for
|
|
@@ -248,6 +249,23 @@ export interface PostgresCollectionConfig<M extends Record<string, unknown> = Re
|
|
|
248
249
|
* @see SearchConfig
|
|
249
250
|
*/
|
|
250
251
|
search?: SearchConfig;
|
|
252
|
+
/**
|
|
253
|
+
* Ordinary indexes on this collection's table.
|
|
254
|
+
*
|
|
255
|
+
* Collection-level, not per-property, because an index over two columns
|
|
256
|
+
* has no single property to hang on and a partial index has none at all —
|
|
257
|
+
* and because a second declaration site for the single-column case would
|
|
258
|
+
* put the same object in two places. An index's identity is a column list
|
|
259
|
+
* in an order; the single-column case is a degenerate one, not a special
|
|
260
|
+
* one.
|
|
261
|
+
*
|
|
262
|
+
* `VectorProperty.index` stays where it is: an ANN structure is a property
|
|
263
|
+
* of the column's type, not of a query.
|
|
264
|
+
*
|
|
265
|
+
* Postgres-only, like {@link SearchConfig}: refused on another engine
|
|
266
|
+
* rather than silently ignored.
|
|
267
|
+
*/
|
|
268
|
+
indexes?: readonly CollectionIndex<Extract<keyof M, string>>[];
|
|
251
269
|
}
|
|
252
270
|
/**
|
|
253
271
|
* A collection backed by Firebase / Firestore.
|
|
@@ -537,7 +555,7 @@ export interface AuthCollectionConfig {
|
|
|
537
555
|
*
|
|
538
556
|
* Set to `false` to disable, or pass a custom `EntityAction` to replace the UI.
|
|
539
557
|
*
|
|
540
|
-
* The object form is an `EntityAction` from `@rebasepro/
|
|
558
|
+
* The object form is an `EntityAction` from `@rebasepro/cms-types`, typed
|
|
541
559
|
* here as `object` because it is a React component with admin controllers in
|
|
542
560
|
* its props and nothing on the server reads it — only whether the built-in
|
|
543
561
|
* action is injected, which is the boolean.
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
*
|
|
19
19
|
* The cost is that the return type is `unknown` rather than `ReactNode`, so a
|
|
20
20
|
* function that returns something React could not render is accepted here.
|
|
21
|
-
* `@rebasepro/
|
|
21
|
+
* `@rebasepro/cms-types` re-exports a `ReactComponentRef<P>` narrowed against
|
|
22
22
|
* the real `React.ComponentType` for authoring and for the admin's internals,
|
|
23
23
|
* which restores that check where it can be enforced.
|
|
24
24
|
*/
|
package/dist/types/index.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ export * from "./properties.js";
|
|
|
5
5
|
export * from "./admin_block.js";
|
|
6
6
|
export * from "./collections.js";
|
|
7
7
|
export * from "./search.js";
|
|
8
|
+
export * from "./indexes.js";
|
|
8
9
|
export * from "./relations.js";
|
|
9
10
|
export * from "./policy.js";
|
|
10
11
|
export * from "./rls-functions.js";
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ordinary indexes, declared on a collection.
|
|
3
|
+
*
|
|
4
|
+
* Distinct from the two index-shaped things Rebase already builds. A `search`
|
|
5
|
+
* block builds a GIN index over a generated `tsvector`, and a `vector`
|
|
6
|
+
* property builds an ANN index over an embedding; both are structures the
|
|
7
|
+
* *feature* owns and neither is a query the developer wrote. This is the plain
|
|
8
|
+
* case — the btree behind a `where` clause — which had no declaration site at
|
|
9
|
+
* all, so the only way to have one was to write it by hand, where the next
|
|
10
|
+
* `rebase db push` planned it away.
|
|
11
|
+
*
|
|
12
|
+
* Every form here is core Postgres, deliberately. See {@link CollectionIndex}.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* A key column of an index whose access method has no ordering.
|
|
16
|
+
*
|
|
17
|
+
* `gin` and `brin` reject `ASC`/`DESC`/`NULLS` outright — Postgres answers
|
|
18
|
+
* `access method "gin" does not support ASC/DESC options` — so those methods
|
|
19
|
+
* take this narrower shape and the combination is unrepresentable rather than
|
|
20
|
+
* refused at build time.
|
|
21
|
+
*/
|
|
22
|
+
export interface UnorderedIndexKey<Keys extends string = string> {
|
|
23
|
+
/**
|
|
24
|
+
* A property key on this collection — never a column name.
|
|
25
|
+
*
|
|
26
|
+
* Which column that resolves to depends on the property, and the two
|
|
27
|
+
* differ in exactly the case an index is most often wanted for: a
|
|
28
|
+
* `belongsTo` relation compiles to its resolved `localKey`
|
|
29
|
+
* (`primaryCategory` → `primary_category_id`), not to the snake-cased
|
|
30
|
+
* property key. Anything else resolves through `columnName`, or the
|
|
31
|
+
* snake-case default when it declares none.
|
|
32
|
+
*
|
|
33
|
+
* Writing the column name here would work for most properties and quietly
|
|
34
|
+
* index nothing for a foreign key, which is the one people reach for.
|
|
35
|
+
*/
|
|
36
|
+
prop: Keys | (string & {});
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* A key column of an index, when its order matters.
|
|
40
|
+
*
|
|
41
|
+
* `direction` and `nulls` earn their place only when a query's `ORDER BY`
|
|
42
|
+
* mixes directions. A lone `DESC` index is redundant with its `ASC` twin —
|
|
43
|
+
* Postgres scans a btree backwards just as fast — and declaring both is
|
|
44
|
+
* refused.
|
|
45
|
+
*
|
|
46
|
+
* Writing the Postgres default down explicitly is free: the derived name
|
|
47
|
+
* hashes the *effective* order, so adding `direction: "asc"` to a column that
|
|
48
|
+
* was already ascending is not a redefinition and rebuilds nothing.
|
|
49
|
+
*/
|
|
50
|
+
export interface IndexKey<Keys extends string = string> extends UnorderedIndexKey<Keys> {
|
|
51
|
+
direction?: "asc" | "desc";
|
|
52
|
+
/** Postgres's own default: `last` under `asc`, `first` under `desc`. */
|
|
53
|
+
nulls?: "first" | "last";
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* The rows a partial index covers.
|
|
57
|
+
*
|
|
58
|
+
* Structure rather than a SQL string, and this is the most load-bearing choice
|
|
59
|
+
* in the type. A string would be replayed verbatim by Atlas in a scratch
|
|
60
|
+
* database, would be the one place a caller reaches for an extension operator
|
|
61
|
+
* class or a subquery, could not be checked against the collection's
|
|
62
|
+
* properties, and could not be fingerprinted — its own text would have to go
|
|
63
|
+
* into the derived name, so reformatting it would rename a live index.
|
|
64
|
+
*
|
|
65
|
+
* Structure keeps every reference resolvable at build time, keeps literals
|
|
66
|
+
* going through the same quoting as the rest of the DDL, and keeps the name
|
|
67
|
+
* stable under any rendering change.
|
|
68
|
+
*
|
|
69
|
+
* There is no `or`. An OR predicate almost always means the index should not
|
|
70
|
+
* be partial at all; a caller who genuinely needs one declares two indexes.
|
|
71
|
+
*/
|
|
72
|
+
export type IndexPredicate<Keys extends string = string> = {
|
|
73
|
+
prop: Keys | (string & {});
|
|
74
|
+
op: "=";
|
|
75
|
+
value: string | number | boolean;
|
|
76
|
+
} | {
|
|
77
|
+
prop: Keys | (string & {});
|
|
78
|
+
op: "!=" | "<" | "<=" | ">" | ">=";
|
|
79
|
+
value: string | number;
|
|
80
|
+
} | {
|
|
81
|
+
prop: Keys | (string & {});
|
|
82
|
+
op: "is null" | "is not null";
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* A non-empty list, enforced in the type. An empty `IN` is a predicate
|
|
86
|
+
* matching nothing: it builds an index over zero rows and reports success,
|
|
87
|
+
* which is the silent-empty-condition shape this codebase has been bitten
|
|
88
|
+
* by before.
|
|
89
|
+
*/
|
|
90
|
+
| {
|
|
91
|
+
prop: Keys | (string & {});
|
|
92
|
+
op: "in";
|
|
93
|
+
value: readonly [string | number, ...(string | number)[]];
|
|
94
|
+
} | {
|
|
95
|
+
and: readonly [IndexPredicate<Keys>, ...IndexPredicate<Keys>[]];
|
|
96
|
+
};
|
|
97
|
+
interface BaseCollectionIndex<Keys extends string = string> {
|
|
98
|
+
/**
|
|
99
|
+
* The key columns, in order. This *is* the index's identity.
|
|
100
|
+
*
|
|
101
|
+
* Postgres can only use a leading subset, so `["ownerId", "createdAt"]`
|
|
102
|
+
* serves a query filtering on `ownerId`, and one filtering on both, and
|
|
103
|
+
* never one filtering on `createdAt` alone.
|
|
104
|
+
*
|
|
105
|
+
* Capped at five keys. Postgres allows thirty-two; past four the trailing
|
|
106
|
+
* columns are dead weight on every write, and the declaration is usually
|
|
107
|
+
* someone hoping a query gets faster by accretion. Payload columns that
|
|
108
|
+
* are not searched belong in `include`, which does not count against this.
|
|
109
|
+
*/
|
|
110
|
+
on: readonly [Keys | IndexKey<Keys>, ...(Keys | IndexKey<Keys>)[]];
|
|
111
|
+
where?: IndexPredicate<Keys>;
|
|
112
|
+
/**
|
|
113
|
+
* Why this index exists, in one line. Required, and the only required
|
|
114
|
+
* field carrying no SQL.
|
|
115
|
+
*
|
|
116
|
+
* An index is the only thing a Rebase config can declare that costs money
|
|
117
|
+
* forever and whose benefit is invisible from the config. `rebase doctor`
|
|
118
|
+
* prints this beside "0 scans in 34 days, 412 MB", which is the one moment
|
|
119
|
+
* anyone is in a position to decide whether to delete it. Without it
|
|
120
|
+
* nobody can decide, so nobody does, and the table accretes indexes for
|
|
121
|
+
* the life of the product.
|
|
122
|
+
*/
|
|
123
|
+
reason: string;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* The default. Answers equality, range, `ORDER BY`, and uniqueness.
|
|
127
|
+
*/
|
|
128
|
+
export interface BtreeIndex<Keys extends string = string> extends BaseCollectionIndex<Keys> {
|
|
129
|
+
using?: "btree";
|
|
130
|
+
/**
|
|
131
|
+
* A composite uniqueness guarantee.
|
|
132
|
+
*
|
|
133
|
+
* Single-column uniqueness is `validation.unique` on the property, and
|
|
134
|
+
* declaring it here is refused rather than accepted as a synonym.
|
|
135
|
+
* `validation.unique` compiles to an inline `UNIQUE` whose backing index
|
|
136
|
+
* Postgres — not Rebase — names `<table>_<column>_key`. That name is in
|
|
137
|
+
* every deployed database, appears in no contract file, and no release can
|
|
138
|
+
* reach in and rename it.
|
|
139
|
+
*/
|
|
140
|
+
unique?: boolean;
|
|
141
|
+
/**
|
|
142
|
+
* Payload columns carried in the leaf pages, for index-only scans. Not
|
|
143
|
+
* searchable and not ordered — they save a heap fetch at the cost of a
|
|
144
|
+
* fatter index. May not overlap `on`.
|
|
145
|
+
*/
|
|
146
|
+
include?: readonly (Keys | (string & {}))[];
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Containment over an `array` property or a JSONB `map`, using core operator
|
|
150
|
+
* classes only. Trigram and full-text search are `search:`, not this.
|
|
151
|
+
*/
|
|
152
|
+
export interface GinIndex<Keys extends string = string> extends BaseCollectionIndex<Keys> {
|
|
153
|
+
using: "gin";
|
|
154
|
+
on: readonly [Keys | UnorderedIndexKey<Keys>, ...(Keys | UnorderedIndexKey<Keys>)[]];
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* A naturally-ordered column on an append-only table — tiny, and useless the
|
|
158
|
+
* moment rows arrive out of order.
|
|
159
|
+
*/
|
|
160
|
+
export interface BrinIndex<Keys extends string = string> extends BaseCollectionIndex<Keys> {
|
|
161
|
+
using: "brin";
|
|
162
|
+
on: readonly [Keys | UnorderedIndexKey<Keys>, ...(Keys | UnorderedIndexKey<Keys>)[]];
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* An index on a collection's table.
|
|
166
|
+
*
|
|
167
|
+
* No `gist` and no `hash`: every interesting gist operator class ships in an
|
|
168
|
+
* extension, and hash indexes cannot be unique, composite, or ordered.
|
|
169
|
+
*
|
|
170
|
+
* The restriction to core Postgres is not conservatism, it is what keeps the
|
|
171
|
+
* whole model on the Atlas path. `rebase db push` materialises the desired
|
|
172
|
+
* state in a bare scratch database to plan against, `--exclude` does not
|
|
173
|
+
* suppress that replay, and `CREATE EXTENSION` cannot be put in the file — so
|
|
174
|
+
* an index needing `gin_trgm_ops` or `vector_cosine_ops` is refused at build
|
|
175
|
+
* time rather than emitted to fail later against a database the author has
|
|
176
|
+
* never heard of. Trigram search is `search:`; ANN is a `vector` property.
|
|
177
|
+
*/
|
|
178
|
+
export type CollectionIndex<Keys extends string = string> = BtreeIndex<Keys> | GinIndex<Keys> | BrinIndex<Keys>;
|
|
179
|
+
export {};
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* They were spread across three places that had nothing to do with each other:
|
|
10
10
|
* the `Table*` shapes sat in `websockets.ts`, next to the WebSocket frame types
|
|
11
11
|
* they share no relationship with, and `PostgresPolicy` was declared twice — in
|
|
12
|
-
* `@rebasepro/
|
|
12
|
+
* `@rebasepro/cms`'s RLS tab and again in `@rebasepro/studio`'s RLS editor,
|
|
13
13
|
* the second with a comment explaining it was inline "to avoid depending on
|
|
14
14
|
* @rebasepro/studio". Neither had to: this package is already a dependency of
|
|
15
15
|
* both.
|
|
@@ -625,7 +625,7 @@ export interface MapProperty extends BaseProperty {
|
|
|
625
625
|
* rest of the map's presentation options: `sortProperties` in
|
|
626
626
|
* `@rebasepro/common` reads it recursively, and `@rebasepro/firebase` calls
|
|
627
627
|
* that when it builds collections. A core package cannot read the admin
|
|
628
|
-
* block — the field exists only once `@rebasepro/
|
|
628
|
+
* block — the field exists only once `@rebasepro/cms-types` is installed.
|
|
629
629
|
*/
|
|
630
630
|
propertiesOrder?: string[];
|
|
631
631
|
/**
|
package/package.json
CHANGED
package/src/types/admin_block.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* There is no *type* for the block in this package any more, and that is the point:
|
|
5
5
|
* `admin` is not declared on `BaseCollectionConfig` or on any property here, so a
|
|
6
|
-
* BaaS install cannot even write one. `@rebasepro/
|
|
6
|
+
* BaaS install cannot even write one. `@rebasepro/cms-types` adds the field back by
|
|
7
7
|
* declaration merging, which is why installing it is what makes the admin surface
|
|
8
8
|
* appear.
|
|
9
9
|
*
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* Every key that belongs inside a collection's `admin` block, as data.
|
|
16
16
|
*
|
|
17
17
|
* The type that describes these fields is `AdminCollectionOptions` in
|
|
18
|
-
* `@rebasepro/
|
|
18
|
+
* `@rebasepro/cms-types`, and it is erased at build time — but three runtime
|
|
19
19
|
* consumers need the list, and two of them are core:
|
|
20
20
|
*
|
|
21
21
|
* - `serializeCollections`, to drop the block from the contract
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
* backend ignores it and the panel never finds it again.
|
|
26
26
|
* - the `collections-admin-block` codemod
|
|
27
27
|
*
|
|
28
|
-
* `@rebasepro/
|
|
28
|
+
* `@rebasepro/cms-types` re-exports this and asserts it names only real option
|
|
29
29
|
* keys; the count is pinned by a test there.
|
|
30
30
|
*
|
|
31
31
|
* @group Models
|
|
@@ -81,15 +81,15 @@ export type AdminCollectionKey = typeof ADMIN_COLLECTION_KEYS[number];
|
|
|
81
81
|
* Every key that belongs inside a *property's* `admin` block, as data.
|
|
82
82
|
*
|
|
83
83
|
* The union of `AdminPropertyOptions` and its per-type extensions
|
|
84
|
-
* (`AdminStringOptions`, `AdminArrayOptions`, …) in `@rebasepro/
|
|
84
|
+
* (`AdminStringOptions`, `AdminArrayOptions`, …) in `@rebasepro/cms-types`.
|
|
85
85
|
* It lives here for the same reason {@link ADMIN_COLLECTION_KEYS} does: the
|
|
86
86
|
* runtime consumers are core packages that the BaaS guard forbids from
|
|
87
|
-
* importing `@rebasepro/
|
|
87
|
+
* importing `@rebasepro/cms-types`. Here it is the boot-time collection
|
|
88
88
|
* validator in `@rebasepro/server`, which has to tell "you left `readOnly` at
|
|
89
89
|
* the top of the property, where nothing reads it" apart from "you invented a
|
|
90
90
|
* key we have never heard of".
|
|
91
91
|
*
|
|
92
|
-
* `@rebasepro/
|
|
92
|
+
* `@rebasepro/cms-types` re-exports this and asserts it names only real
|
|
93
93
|
* option keys.
|
|
94
94
|
*
|
|
95
95
|
* @group Models
|
|
@@ -138,7 +138,7 @@ export type AdminPropertyKey = typeof ADMIN_PROPERTY_KEYS[number];
|
|
|
138
138
|
* in favour of the value the user changed away from.
|
|
139
139
|
*
|
|
140
140
|
* This lives here, next to the key lists, because it had two implementations —
|
|
141
|
-
* `toAdminCollectionConfig` in `@rebasepro/
|
|
141
|
+
* `toAdminCollectionConfig` in `@rebasepro/cms-types` and `nestAdminKeys` in
|
|
142
142
|
* `@rebasepro/server`'s schema editor — that agreed on everything except that
|
|
143
143
|
* precedence, which is the only part that decides whether a save is visible.
|
|
144
144
|
*
|
package/src/types/collections.ts
CHANGED
|
@@ -9,6 +9,7 @@ import type { SecurityRule } from "./security_rules";
|
|
|
9
9
|
import { getDataSourceCapabilities } from "./data_source";
|
|
10
10
|
import type { WhereFilterOp, FilterValues, FilterPreset } from "./filter-operators";
|
|
11
11
|
import type { SearchConfig } from "./search";
|
|
12
|
+
import type { CollectionIndex } from "./indexes";
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Base interface containing all driver-agnostic collection properties.
|
|
@@ -323,6 +324,24 @@ export interface PostgresCollectionConfig<M extends Record<string, unknown> = Re
|
|
|
323
324
|
* @see SearchConfig
|
|
324
325
|
*/
|
|
325
326
|
search?: SearchConfig;
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Ordinary indexes on this collection's table.
|
|
330
|
+
*
|
|
331
|
+
* Collection-level, not per-property, because an index over two columns
|
|
332
|
+
* has no single property to hang on and a partial index has none at all —
|
|
333
|
+
* and because a second declaration site for the single-column case would
|
|
334
|
+
* put the same object in two places. An index's identity is a column list
|
|
335
|
+
* in an order; the single-column case is a degenerate one, not a special
|
|
336
|
+
* one.
|
|
337
|
+
*
|
|
338
|
+
* `VectorProperty.index` stays where it is: an ANN structure is a property
|
|
339
|
+
* of the column's type, not of a query.
|
|
340
|
+
*
|
|
341
|
+
* Postgres-only, like {@link SearchConfig}: refused on another engine
|
|
342
|
+
* rather than silently ignored.
|
|
343
|
+
*/
|
|
344
|
+
indexes?: readonly CollectionIndex<Extract<keyof M, string>>[];
|
|
326
345
|
}
|
|
327
346
|
|
|
328
347
|
/**
|
|
@@ -681,7 +700,7 @@ export interface AuthCollectionConfig {
|
|
|
681
700
|
*
|
|
682
701
|
* Set to `false` to disable, or pass a custom `EntityAction` to replace the UI.
|
|
683
702
|
*
|
|
684
|
-
* The object form is an `EntityAction` from `@rebasepro/
|
|
703
|
+
* The object form is an `EntityAction` from `@rebasepro/cms-types`, typed
|
|
685
704
|
* here as `object` because it is a React component with admin controllers in
|
|
686
705
|
* its props and nothing on the server reads it — only whether the built-in
|
|
687
706
|
* action is injected, which is the boolean.
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
*
|
|
19
19
|
* The cost is that the return type is `unknown` rather than `ReactNode`, so a
|
|
20
20
|
* function that returns something React could not render is accepted here.
|
|
21
|
-
* `@rebasepro/
|
|
21
|
+
* `@rebasepro/cms-types` re-exports a `ReactComponentRef<P>` narrowed against
|
|
22
22
|
* the real `React.ComponentType` for authoring and for the admin's internals,
|
|
23
23
|
* which restores that check where it can be enforced.
|
|
24
24
|
*/
|
package/src/types/index.ts
CHANGED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ordinary indexes, declared on a collection.
|
|
3
|
+
*
|
|
4
|
+
* Distinct from the two index-shaped things Rebase already builds. A `search`
|
|
5
|
+
* block builds a GIN index over a generated `tsvector`, and a `vector`
|
|
6
|
+
* property builds an ANN index over an embedding; both are structures the
|
|
7
|
+
* *feature* owns and neither is a query the developer wrote. This is the plain
|
|
8
|
+
* case — the btree behind a `where` clause — which had no declaration site at
|
|
9
|
+
* all, so the only way to have one was to write it by hand, where the next
|
|
10
|
+
* `rebase db push` planned it away.
|
|
11
|
+
*
|
|
12
|
+
* Every form here is core Postgres, deliberately. See {@link CollectionIndex}.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* A key column of an index whose access method has no ordering.
|
|
17
|
+
*
|
|
18
|
+
* `gin` and `brin` reject `ASC`/`DESC`/`NULLS` outright — Postgres answers
|
|
19
|
+
* `access method "gin" does not support ASC/DESC options` — so those methods
|
|
20
|
+
* take this narrower shape and the combination is unrepresentable rather than
|
|
21
|
+
* refused at build time.
|
|
22
|
+
*/
|
|
23
|
+
export interface UnorderedIndexKey<Keys extends string = string> {
|
|
24
|
+
/**
|
|
25
|
+
* A property key on this collection — never a column name.
|
|
26
|
+
*
|
|
27
|
+
* Which column that resolves to depends on the property, and the two
|
|
28
|
+
* differ in exactly the case an index is most often wanted for: a
|
|
29
|
+
* `belongsTo` relation compiles to its resolved `localKey`
|
|
30
|
+
* (`primaryCategory` → `primary_category_id`), not to the snake-cased
|
|
31
|
+
* property key. Anything else resolves through `columnName`, or the
|
|
32
|
+
* snake-case default when it declares none.
|
|
33
|
+
*
|
|
34
|
+
* Writing the column name here would work for most properties and quietly
|
|
35
|
+
* index nothing for a foreign key, which is the one people reach for.
|
|
36
|
+
*/
|
|
37
|
+
prop: Keys | (string & {});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* A key column of an index, when its order matters.
|
|
42
|
+
*
|
|
43
|
+
* `direction` and `nulls` earn their place only when a query's `ORDER BY`
|
|
44
|
+
* mixes directions. A lone `DESC` index is redundant with its `ASC` twin —
|
|
45
|
+
* Postgres scans a btree backwards just as fast — and declaring both is
|
|
46
|
+
* refused.
|
|
47
|
+
*
|
|
48
|
+
* Writing the Postgres default down explicitly is free: the derived name
|
|
49
|
+
* hashes the *effective* order, so adding `direction: "asc"` to a column that
|
|
50
|
+
* was already ascending is not a redefinition and rebuilds nothing.
|
|
51
|
+
*/
|
|
52
|
+
export interface IndexKey<Keys extends string = string> extends UnorderedIndexKey<Keys> {
|
|
53
|
+
direction?: "asc" | "desc";
|
|
54
|
+
/** Postgres's own default: `last` under `asc`, `first` under `desc`. */
|
|
55
|
+
nulls?: "first" | "last";
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* The rows a partial index covers.
|
|
60
|
+
*
|
|
61
|
+
* Structure rather than a SQL string, and this is the most load-bearing choice
|
|
62
|
+
* in the type. A string would be replayed verbatim by Atlas in a scratch
|
|
63
|
+
* database, would be the one place a caller reaches for an extension operator
|
|
64
|
+
* class or a subquery, could not be checked against the collection's
|
|
65
|
+
* properties, and could not be fingerprinted — its own text would have to go
|
|
66
|
+
* into the derived name, so reformatting it would rename a live index.
|
|
67
|
+
*
|
|
68
|
+
* Structure keeps every reference resolvable at build time, keeps literals
|
|
69
|
+
* going through the same quoting as the rest of the DDL, and keeps the name
|
|
70
|
+
* stable under any rendering change.
|
|
71
|
+
*
|
|
72
|
+
* There is no `or`. An OR predicate almost always means the index should not
|
|
73
|
+
* be partial at all; a caller who genuinely needs one declares two indexes.
|
|
74
|
+
*/
|
|
75
|
+
export type IndexPredicate<Keys extends string = string> =
|
|
76
|
+
| { prop: Keys | (string & {}); op: "="; value: string | number | boolean }
|
|
77
|
+
| { prop: Keys | (string & {}); op: "!=" | "<" | "<=" | ">" | ">="; value: string | number }
|
|
78
|
+
| { prop: Keys | (string & {}); op: "is null" | "is not null" }
|
|
79
|
+
/**
|
|
80
|
+
* A non-empty list, enforced in the type. An empty `IN` is a predicate
|
|
81
|
+
* matching nothing: it builds an index over zero rows and reports success,
|
|
82
|
+
* which is the silent-empty-condition shape this codebase has been bitten
|
|
83
|
+
* by before.
|
|
84
|
+
*/
|
|
85
|
+
| { prop: Keys | (string & {}); op: "in"; value: readonly [string | number, ...(string | number)[]] }
|
|
86
|
+
| { and: readonly [IndexPredicate<Keys>, ...IndexPredicate<Keys>[]] };
|
|
87
|
+
|
|
88
|
+
interface BaseCollectionIndex<Keys extends string = string> {
|
|
89
|
+
/**
|
|
90
|
+
* The key columns, in order. This *is* the index's identity.
|
|
91
|
+
*
|
|
92
|
+
* Postgres can only use a leading subset, so `["ownerId", "createdAt"]`
|
|
93
|
+
* serves a query filtering on `ownerId`, and one filtering on both, and
|
|
94
|
+
* never one filtering on `createdAt` alone.
|
|
95
|
+
*
|
|
96
|
+
* Capped at five keys. Postgres allows thirty-two; past four the trailing
|
|
97
|
+
* columns are dead weight on every write, and the declaration is usually
|
|
98
|
+
* someone hoping a query gets faster by accretion. Payload columns that
|
|
99
|
+
* are not searched belong in `include`, which does not count against this.
|
|
100
|
+
*/
|
|
101
|
+
on: readonly [Keys | IndexKey<Keys>, ...(Keys | IndexKey<Keys>)[]];
|
|
102
|
+
|
|
103
|
+
where?: IndexPredicate<Keys>;
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Why this index exists, in one line. Required, and the only required
|
|
107
|
+
* field carrying no SQL.
|
|
108
|
+
*
|
|
109
|
+
* An index is the only thing a Rebase config can declare that costs money
|
|
110
|
+
* forever and whose benefit is invisible from the config. `rebase doctor`
|
|
111
|
+
* prints this beside "0 scans in 34 days, 412 MB", which is the one moment
|
|
112
|
+
* anyone is in a position to decide whether to delete it. Without it
|
|
113
|
+
* nobody can decide, so nobody does, and the table accretes indexes for
|
|
114
|
+
* the life of the product.
|
|
115
|
+
*/
|
|
116
|
+
reason: string;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* The default. Answers equality, range, `ORDER BY`, and uniqueness.
|
|
121
|
+
*/
|
|
122
|
+
export interface BtreeIndex<Keys extends string = string> extends BaseCollectionIndex<Keys> {
|
|
123
|
+
using?: "btree";
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* A composite uniqueness guarantee.
|
|
127
|
+
*
|
|
128
|
+
* Single-column uniqueness is `validation.unique` on the property, and
|
|
129
|
+
* declaring it here is refused rather than accepted as a synonym.
|
|
130
|
+
* `validation.unique` compiles to an inline `UNIQUE` whose backing index
|
|
131
|
+
* Postgres — not Rebase — names `<table>_<column>_key`. That name is in
|
|
132
|
+
* every deployed database, appears in no contract file, and no release can
|
|
133
|
+
* reach in and rename it.
|
|
134
|
+
*/
|
|
135
|
+
unique?: boolean;
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Payload columns carried in the leaf pages, for index-only scans. Not
|
|
139
|
+
* searchable and not ordered — they save a heap fetch at the cost of a
|
|
140
|
+
* fatter index. May not overlap `on`.
|
|
141
|
+
*/
|
|
142
|
+
include?: readonly (Keys | (string & {}))[];
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Containment over an `array` property or a JSONB `map`, using core operator
|
|
147
|
+
* classes only. Trigram and full-text search are `search:`, not this.
|
|
148
|
+
*/
|
|
149
|
+
export interface GinIndex<Keys extends string = string> extends BaseCollectionIndex<Keys> {
|
|
150
|
+
using: "gin";
|
|
151
|
+
on: readonly [Keys | UnorderedIndexKey<Keys>, ...(Keys | UnorderedIndexKey<Keys>)[]];
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* A naturally-ordered column on an append-only table — tiny, and useless the
|
|
156
|
+
* moment rows arrive out of order.
|
|
157
|
+
*/
|
|
158
|
+
export interface BrinIndex<Keys extends string = string> extends BaseCollectionIndex<Keys> {
|
|
159
|
+
using: "brin";
|
|
160
|
+
on: readonly [Keys | UnorderedIndexKey<Keys>, ...(Keys | UnorderedIndexKey<Keys>)[]];
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* An index on a collection's table.
|
|
165
|
+
*
|
|
166
|
+
* No `gist` and no `hash`: every interesting gist operator class ships in an
|
|
167
|
+
* extension, and hash indexes cannot be unique, composite, or ordered.
|
|
168
|
+
*
|
|
169
|
+
* The restriction to core Postgres is not conservatism, it is what keeps the
|
|
170
|
+
* whole model on the Atlas path. `rebase db push` materialises the desired
|
|
171
|
+
* state in a bare scratch database to plan against, `--exclude` does not
|
|
172
|
+
* suppress that replay, and `CREATE EXTENSION` cannot be put in the file — so
|
|
173
|
+
* an index needing `gin_trgm_ops` or `vector_cosine_ops` is refused at build
|
|
174
|
+
* time rather than emitted to fail later against a database the author has
|
|
175
|
+
* never heard of. Trigram search is `search:`; ANN is a `vector` property.
|
|
176
|
+
*/
|
|
177
|
+
export type CollectionIndex<Keys extends string = string> =
|
|
178
|
+
| BtreeIndex<Keys>
|
|
179
|
+
| GinIndex<Keys>
|
|
180
|
+
| BrinIndex<Keys>;
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* They were spread across three places that had nothing to do with each other:
|
|
10
10
|
* the `Table*` shapes sat in `websockets.ts`, next to the WebSocket frame types
|
|
11
11
|
* they share no relationship with, and `PostgresPolicy` was declared twice — in
|
|
12
|
-
* `@rebasepro/
|
|
12
|
+
* `@rebasepro/cms`'s RLS tab and again in `@rebasepro/studio`'s RLS editor,
|
|
13
13
|
* the second with a comment explaining it was inline "to avoid depending on
|
|
14
14
|
* @rebasepro/studio". Neither had to: this package is already a dependency of
|
|
15
15
|
* both.
|
package/src/types/properties.ts
CHANGED
|
@@ -712,7 +712,7 @@ export interface MapProperty extends BaseProperty {
|
|
|
712
712
|
* rest of the map's presentation options: `sortProperties` in
|
|
713
713
|
* `@rebasepro/common` reads it recursively, and `@rebasepro/firebase` calls
|
|
714
714
|
* that when it builds collections. A core package cannot read the admin
|
|
715
|
-
* block — the field exists only once `@rebasepro/
|
|
715
|
+
* block — the field exists only once `@rebasepro/cms-types` is installed.
|
|
716
716
|
*/
|
|
717
717
|
propertiesOrder?: string[];
|
|
718
718
|
/**
|