palgres 0.3.1 → 0.3.2
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/LICENSE +11 -5
- package/dist/chunks/{FAZVO74A.js → DNJLPMEG.js} +2 -2
- package/dist/chunks/FXAZO7GY.js +2 -0
- package/dist/chunks/{OBLAMOLO.js → OBMTT3YH.js} +2 -2
- package/dist/dsl.js +2 -2
- package/dist/index.js +2 -2
- package/dist/testing.js +2 -2
- package/package.json +2 -2
- package/types/palgres/src/dsl/columns.d.ts +234 -27
- package/types/palgres/src/dsl/foreign-key.d.ts +90 -0
- package/types/palgres/src/dsl/policy.d.ts +290 -1
- package/types/palgres/src/dsl/raw.d.ts +56 -0
- package/types/palgres/src/dsl/schema.d.ts +215 -69
- package/dist/chunks/7255GQJI.js +0 -2
|
@@ -15,9 +15,28 @@ import { type ColumnMap, type TableHandle } from './schema.js';
|
|
|
15
15
|
* .to("authenticated")
|
|
16
16
|
* .using("owner = (select auth.uid())");
|
|
17
17
|
*/
|
|
18
|
+
/**
|
|
19
|
+
* A structured policy expression, as plain data: one node of the tree that a
|
|
20
|
+
* `USING`, `WITH CHECK` or CHECK expression is made of.
|
|
21
|
+
*
|
|
22
|
+
* You do not write these nodes by hand. Build them with the context passed to a
|
|
23
|
+
* table's `policies` (or `checks`) callback: `p.col(...)`, `p.auth.uid()`,
|
|
24
|
+
* `p.existsIn(...)` and the methods of {@link PolicyExprRef}. Unlike a raw SQL
|
|
25
|
+
* string, a structured expression has its column names checked at compile time,
|
|
26
|
+
* and `auth.uid()` is always written as `(SELECT auth.uid())`.
|
|
27
|
+
*/
|
|
18
28
|
export type PolicyExpr = {
|
|
19
29
|
kind: "col";
|
|
20
30
|
name: string;
|
|
31
|
+
/**
|
|
32
|
+
* Which table the column belongs to. When absent, the column is one of
|
|
33
|
+
* the policy's own table. When set, it is the alias of the table inside
|
|
34
|
+
* an `existsIn` subquery.
|
|
35
|
+
*
|
|
36
|
+
* This is what keeps a correlated subquery correct: without it,
|
|
37
|
+
* `team_id = team_id` would compare a column with itself, be true for
|
|
38
|
+
* every row, and the policy would let every row through.
|
|
39
|
+
*/
|
|
21
40
|
alias?: string;
|
|
22
41
|
} | {
|
|
23
42
|
kind: "authUid";
|
|
@@ -43,13 +62,51 @@ export type PolicyExpr = {
|
|
|
43
62
|
expr: PolicyExpr;
|
|
44
63
|
negated: boolean;
|
|
45
64
|
} | {
|
|
65
|
+
/**
|
|
66
|
+
* A correlated `EXISTS (SELECT 1 FROM <table> <alias> WHERE <predicate>)`
|
|
67
|
+
* subquery.
|
|
68
|
+
*
|
|
69
|
+
* The subquery is written directly into the policy, not wrapped in a
|
|
70
|
+
* `SECURITY DEFINER` function, so the row-level security of the table it
|
|
71
|
+
* reads still applies inside it: it cannot reveal rows the caller could
|
|
72
|
+
* not read anyway.
|
|
73
|
+
*/
|
|
46
74
|
kind: "exists";
|
|
47
75
|
table: string;
|
|
48
76
|
alias: string;
|
|
77
|
+
/**
|
|
78
|
+
* The name of the policy's own table. The correlation needs it.
|
|
79
|
+
*
|
|
80
|
+
* Inside the subquery, Postgres resolves an unqualified column name to
|
|
81
|
+
* the inner table. A column of the policy's own table is therefore
|
|
82
|
+
* written qualified with this name (for example
|
|
83
|
+
* `"public"."documents"."team_id"`).
|
|
84
|
+
* Left unqualified, it would compare the inner column with itself
|
|
85
|
+
* (`pb_e1.team_id = pb_e1.team_id`), be true for every row, and every
|
|
86
|
+
* user would see every row.
|
|
87
|
+
*/
|
|
49
88
|
outer: string;
|
|
50
89
|
predicate: PolicyExpr;
|
|
51
90
|
};
|
|
91
|
+
/**
|
|
92
|
+
* The comparison operators of a policy expression: `eq` (`=`), `neq` (`<>`),
|
|
93
|
+
* `gt` (`>`), `gte` (`>=`), `lt` (`<`) and `lte` (`<=`). `in` takes a list on
|
|
94
|
+
* its right-hand side; no builder method produces it, and neither `check()` nor
|
|
95
|
+
* `policySQL()` accepts it.
|
|
96
|
+
*/
|
|
52
97
|
export type PolicyBinOp = "eq" | "neq" | "gt" | "gte" | "lt" | "lte" | "in";
|
|
98
|
+
/**
|
|
99
|
+
* A policy expression you can keep building on.
|
|
100
|
+
*
|
|
101
|
+
* You get one from the policy context (`p.col("owner")`, `p.auth.uid()`,
|
|
102
|
+
* `p.existsIn(...)`) and chain comparisons and logic onto it: `.eq()`,
|
|
103
|
+
* `.isNull()`, `.and()`, `.not()` and the rest. Every method returns a new
|
|
104
|
+
* `PolicyExprRef`; the expression itself stays plain data in `expr`. Pass the
|
|
105
|
+
* result to `.using()`, `.withCheck()` or `check()`.
|
|
106
|
+
*
|
|
107
|
+
* @example
|
|
108
|
+
* p.col("owner").eq(p.auth.uid()).and(p.col("deleted_at").isNull())
|
|
109
|
+
*/
|
|
53
110
|
export declare class PolicyExprRef {
|
|
54
111
|
readonly expr: PolicyExpr;
|
|
55
112
|
constructor(expr: PolicyExpr);
|
|
@@ -60,47 +117,216 @@ export declare class PolicyExprRef {
|
|
|
60
117
|
gte(other: PolicyOperand): PolicyExprRef;
|
|
61
118
|
lt(other: PolicyOperand): PolicyExprRef;
|
|
62
119
|
lte(other: PolicyOperand): PolicyExprRef;
|
|
120
|
+
/**
|
|
121
|
+
* `IS NULL`, or `IS NOT NULL` when `negated` is `true`.
|
|
122
|
+
*
|
|
123
|
+
* Use this rather than `.eq(null)`: in SQL, `= NULL` is never true, it is
|
|
124
|
+
* always unknown. A common use is a partial index or policy condition such as
|
|
125
|
+
* `deleted_at IS NULL`.
|
|
126
|
+
*
|
|
127
|
+
* @param negated `true` for `IS NOT NULL`. Default `false`.
|
|
128
|
+
*/
|
|
63
129
|
isNull(negated?: boolean): PolicyExprRef;
|
|
64
130
|
and(...others: PolicyExprRef[]): PolicyExprRef;
|
|
65
131
|
or(...others: PolicyExprRef[]): PolicyExprRef;
|
|
66
132
|
not(): PolicyExprRef;
|
|
67
133
|
}
|
|
134
|
+
/**
|
|
135
|
+
* A type annotation for a table used in `existsIn`, for when two table
|
|
136
|
+
* declarations refer to each other.
|
|
137
|
+
*
|
|
138
|
+
* Example: `memberships.household_id` references `households.id`, and the
|
|
139
|
+
* policy of `households` uses `existsIn(memberships, ...)`. Each `const` then
|
|
140
|
+
* needs the other to be initialized, and TypeScript gives up on both with
|
|
141
|
+
* error TS7022 and types them as `any`. That is worse than an error message:
|
|
142
|
+
* with `m.col(...)` typed as `any`, a misspelled column name in the policy
|
|
143
|
+
* compiles silently, and a row-level-security rule fails open. Only a type
|
|
144
|
+
* annotation on one of the two declarations breaks the cycle.
|
|
145
|
+
*
|
|
146
|
+
* `TableRef` is that annotation. It is the table-level counterpart of
|
|
147
|
+
* `references((): AnyColumn => y.id)`, but unlike `AnyColumn` it keeps the
|
|
148
|
+
* column names:
|
|
149
|
+
*
|
|
150
|
+
* ```ts
|
|
151
|
+
* const memberships: TableRef<"id" | "household_id" | "user_id", "memberships"> =
|
|
152
|
+
* defineTable("memberships", {
|
|
153
|
+
* columns: {
|
|
154
|
+
* id: uuid().primaryKey(),
|
|
155
|
+
* household_id: uuid().references(() => households.id),
|
|
156
|
+
* user_id: text().notNull(),
|
|
157
|
+
* },
|
|
158
|
+
* });
|
|
159
|
+
* ```
|
|
160
|
+
*
|
|
161
|
+
* Because the names stay in `Cols`, `m.col("user")` still does not compile, and
|
|
162
|
+
* because `N` is given, the table keeps its key inside `defineSchema`. There is
|
|
163
|
+
* deliberately no looser `AnyTable` type that drops the column names: it would
|
|
164
|
+
* silence the cycle and quietly turn off column checking in your policies.
|
|
165
|
+
*
|
|
166
|
+
* @typeParam Cols The table's column names.
|
|
167
|
+
* @typeParam N The table's name.
|
|
168
|
+
*/
|
|
68
169
|
export type TableRef<Cols extends string, N extends string = string> = TableHandle<Record<Cols, ColumnMap[string]>, N>;
|
|
170
|
+
/**
|
|
171
|
+
* The right-hand side of a comparison such as `.eq()`: another expression or a
|
|
172
|
+
* plain value.
|
|
173
|
+
*
|
|
174
|
+
* A plain value is always a literal, never a column: `.eq("owner")` compares
|
|
175
|
+
* with the string `'owner'`, not with the `owner` column. To compare with a
|
|
176
|
+
* column, write `.eq(p.col("owner"))`.
|
|
177
|
+
*/
|
|
69
178
|
export type PolicyOperand = PolicyExprRef | string | number | boolean | null;
|
|
179
|
+
/**
|
|
180
|
+
* The context for writing an expression over one table's columns. A table's
|
|
181
|
+
* `policies`, `checks`, `indexes` and `foreignKeys` callbacks receive it.
|
|
182
|
+
*
|
|
183
|
+
* Column names are limited to the table's own columns, so a misspelled column
|
|
184
|
+
* is a compile error. This works because those options are callbacks, not
|
|
185
|
+
* plain arrays: TypeScript knows the table's columns when it types the
|
|
186
|
+
* callback, while a bare `policy("x")` could not know which table it is on.
|
|
187
|
+
*
|
|
188
|
+
* @typeParam Cols The table's column names.
|
|
189
|
+
*/
|
|
70
190
|
export interface PolicyExprCtx<Cols extends string = string> {
|
|
71
|
-
/**
|
|
191
|
+
/** A column of this table. An unknown column name does not compile. */
|
|
72
192
|
col(name: Cols): PolicyExprRef;
|
|
193
|
+
/**
|
|
194
|
+
* "Does this row belong to a team I am a member of?": membership-based
|
|
195
|
+
* multi-tenancy, as a correlated `EXISTS` subquery on another table.
|
|
196
|
+
*
|
|
197
|
+
* ```ts
|
|
198
|
+
* policies: (p) => [
|
|
199
|
+
* policy("team_read").for("select").using(
|
|
200
|
+
* p.existsIn(teamMembers, (m) =>
|
|
201
|
+
* m.col("team_id").eq(p.col("team_id"))
|
|
202
|
+
* .and(m.col("user_id").eq(p.auth.uid()))),
|
|
203
|
+
* ),
|
|
204
|
+
* ]
|
|
205
|
+
* ```
|
|
206
|
+
*
|
|
207
|
+
* `m` gives the columns of the membership table, `p` those of this table.
|
|
208
|
+
* Both are typed, and they are qualified differently in SQL. If they were
|
|
209
|
+
* mixed up, `team_id = team_id` would compare a column with itself and the
|
|
210
|
+
* policy would let every row through.
|
|
211
|
+
*
|
|
212
|
+
* Tables that refer to each other: if the membership table references this
|
|
213
|
+
* table and this table uses `existsIn` on it, annotate one declaration with
|
|
214
|
+
* {@link TableRef}. Otherwise TypeScript types both as `any` (error TS7022)
|
|
215
|
+
* and `m.col(...)` silently accepts any name.
|
|
216
|
+
*
|
|
217
|
+
* Recursion: the membership table's own policy must not use `existsIn` back
|
|
218
|
+
* into this table; give it the simple rule `user_id = auth.uid()`. If two
|
|
219
|
+
* tables' policies query each other, Postgres fails at query time with
|
|
220
|
+
* `infinite recursion detected in policy for relation ...`. Keep it one-way:
|
|
221
|
+
* the membership table has the simple policy, and every other table
|
|
222
|
+
* subqueries into it.
|
|
223
|
+
*
|
|
224
|
+
* @param table The table to look in, for example the membership table.
|
|
225
|
+
* @param predicate Builds the `WHERE` condition of the subquery from that
|
|
226
|
+
* table's columns (`m`) and this table's (`p`).
|
|
227
|
+
*/
|
|
73
228
|
existsIn<C extends ColumnMap, N extends string>(table: TableHandle<C, N>, predicate: (row: PolicyExprCtx<Extract<keyof C, string>>) => PolicyExprRef): PolicyExprRef;
|
|
229
|
+
/**
|
|
230
|
+
* The caller. `p.auth.uid()` is the signed-in user's id. It is written into
|
|
231
|
+
* SQL as `(SELECT auth.uid())`, so Postgres evaluates it once per statement
|
|
232
|
+
* rather than once per row. Not allowed in a CHECK constraint.
|
|
233
|
+
*/
|
|
74
234
|
auth: {
|
|
75
235
|
uid(): PolicyExprRef;
|
|
76
236
|
};
|
|
237
|
+
/** A plain value, for saying explicitly that one side of a comparison is a literal. */
|
|
77
238
|
lit(v: string | number | boolean | null): PolicyExprRef;
|
|
78
239
|
and(...parts: PolicyExprRef[]): PolicyExprRef;
|
|
79
240
|
or(...parts: PolicyExprRef[]): PolicyExprRef;
|
|
80
241
|
not(part: PolicyExprRef): PolicyExprRef;
|
|
81
242
|
}
|
|
243
|
+
/**
|
|
244
|
+
* Builds an expression context ({@link PolicyExprCtx}) by hand.
|
|
245
|
+
*
|
|
246
|
+
* `defineTable` builds one for you, with the table's own column names, and
|
|
247
|
+
* passes it to the `policies`, `checks`, `indexes` and `foreignKeys`
|
|
248
|
+
* callbacks. Call this yourself only to write an expression outside a table
|
|
249
|
+
* definition.
|
|
250
|
+
*
|
|
251
|
+
* @param _columns The table's column names. Only their type is used: it limits
|
|
252
|
+
* `col()` to these names.
|
|
253
|
+
* @param depth How deep inside `existsIn` subqueries this context is: `0` for
|
|
254
|
+
* the policy's own table. Leave it at the default.
|
|
255
|
+
* @param ownTable The name of the policy's own table, needed for `existsIn`.
|
|
256
|
+
*/
|
|
82
257
|
export declare function exprCtx<Cols extends string = string>(_columns?: readonly Cols[], depth?: number,
|
|
258
|
+
/**
|
|
259
|
+
* The name of the policy's own table. It stays the same at every nesting
|
|
260
|
+
* depth.
|
|
261
|
+
*
|
|
262
|
+
* Inside a subquery, a column without an alias belongs to the policy's own
|
|
263
|
+
* table and is written qualified with this name. That holds for `existsIn`
|
|
264
|
+
* nested inside `existsIn` too, so the policy's own columns can be used at
|
|
265
|
+
* any depth. Without it, an expression that uses `existsIn` cannot be turned
|
|
266
|
+
* into SQL.
|
|
267
|
+
*/
|
|
83
268
|
ownTable?: string): PolicyExprCtx<Cols>;
|
|
84
269
|
/** The SQL command a policy applies to. `"all"` covers SELECT/INSERT/UPDATE/DELETE. */
|
|
85
270
|
export type PolicyCommand = "all" | "select" | "insert" | "update" | "delete";
|
|
86
271
|
/** Whether a policy is permissive (OR-combined, the default) or restrictive
|
|
87
272
|
* (AND-combined). Mirrors Postgres `CREATE POLICY ... AS PERMISSIVE|RESTRICTIVE`. */
|
|
88
273
|
export type PolicyMode = "permissive" | "restrictive";
|
|
274
|
+
/**
|
|
275
|
+
* A finished row-level-security policy, as plain serializable data. `policy()`
|
|
276
|
+
* builds one (it is the builder's `_def`); a table's `policies` callback may
|
|
277
|
+
* also return one directly.
|
|
278
|
+
*
|
|
279
|
+
* - `name`: the policy name, unique per table.
|
|
280
|
+
* - `command`: the SQL command it applies to (`FOR` clause).
|
|
281
|
+
* - `roles`: the database roles it applies to (`TO` clause). An empty array
|
|
282
|
+
* means the policy applies to PUBLIC (all roles), the Postgres default.
|
|
283
|
+
* - `using`: the `USING (...)` row-visibility expression, or `null` when none.
|
|
284
|
+
* - `withCheck`: the `WITH CHECK (...)` write-validation expression, or `null`.
|
|
285
|
+
* - `permissive`: `true` for `AS PERMISSIVE` (default), `false` for restrictive.
|
|
286
|
+
*/
|
|
89
287
|
export interface PolicyDef {
|
|
90
288
|
name: string;
|
|
91
289
|
command: PolicyCommand;
|
|
92
290
|
roles: string[];
|
|
291
|
+
/** A structured expression (preferred) or a raw SQL string, or `null` for none. */
|
|
93
292
|
using: string | PolicyExpr | null;
|
|
94
293
|
withCheck: string | PolicyExpr | null;
|
|
95
294
|
permissive: boolean;
|
|
96
295
|
/** false: the columns it reads get no index of their own from it (withoutIndexes()). */
|
|
97
296
|
indexes?: false;
|
|
98
297
|
}
|
|
298
|
+
/**
|
|
299
|
+
* The Postgres roles a policy can apply to, passed to `.to(...)`.
|
|
300
|
+
*
|
|
301
|
+
* The roles Palbase creates are named literally, so a typo such as
|
|
302
|
+
* `"authenitcated"` is a compile error instead of reaching a migration. Two
|
|
303
|
+
* more forms exist for a database Palbase took over, whose policies may name
|
|
304
|
+
* roles of its own: `"public"` (every role) and `` `role:${name}` `` (a role
|
|
305
|
+
* by its own name); `palgres pull` writes them.
|
|
306
|
+
*
|
|
307
|
+
* You do not need to name the `backend_*` roles: Palbase adds them for you.
|
|
308
|
+
* They are still accepted, so code that names them keeps compiling.
|
|
309
|
+
*/
|
|
99
310
|
export type PolicyRole = "anon" | "authenticated" | "service_role" | "backend_anon" | "backend_authenticated" | "backend_service_role"
|
|
100
311
|
/** Every role (Postgres' PUBLIC): a database taken over may have a policy for it (palgres pull writes it). */
|
|
101
312
|
| "public"
|
|
102
313
|
/** A database role by its own name: what a database taken over has a policy for (palgres pull writes it). */
|
|
103
314
|
| `role:${string}`;
|
|
315
|
+
/**
|
|
316
|
+
* Fluent row-level-security policy builder, returned by {@link policy}.
|
|
317
|
+
*
|
|
318
|
+
* Defaults:
|
|
319
|
+
* - `command`: `"all"`: applies to every SQL command unless `.for(...)` narrows it.
|
|
320
|
+
* - `roles`: `["authenticated"]`: the common case is "this rule applies to
|
|
321
|
+
* signed-in users". Call `.to(...)` to change it; `.to()` with no roles
|
|
322
|
+
* targets PUBLIC (all roles).
|
|
323
|
+
* - `using` / `withCheck`: `null`: no row filter and no write check until set.
|
|
324
|
+
* - `permissive`: `true`: `AS PERMISSIVE` (permissive policies OR together).
|
|
325
|
+
*
|
|
326
|
+
* Each method changes the policy in place and returns the same builder, so
|
|
327
|
+
* calls chain. The finished {@link PolicyDef} is in `_def`; put the builder
|
|
328
|
+
* itself in a table's `policies` list.
|
|
329
|
+
*/
|
|
104
330
|
export declare class PolicyBuilder {
|
|
105
331
|
readonly _def: PolicyDef;
|
|
106
332
|
constructor(name: string);
|
|
@@ -116,6 +342,23 @@ export declare class PolicyBuilder {
|
|
|
116
342
|
* policy("p").to() // PUBLIC
|
|
117
343
|
*/
|
|
118
344
|
to(...roles: PolicyRole[]): this;
|
|
345
|
+
/**
|
|
346
|
+
* The `USING (...)` expression: which existing rows the policy lets the
|
|
347
|
+
* caller see (and update or delete).
|
|
348
|
+
*
|
|
349
|
+
* Prefer a structured expression, built with the context the `policies`
|
|
350
|
+
* callback receives. Its column names are checked at compile time:
|
|
351
|
+
*
|
|
352
|
+
* ```ts
|
|
353
|
+
* policies: (p) => [
|
|
354
|
+
* policy("owner_read").for("select").using(p.col("owner").eq(p.auth.uid())),
|
|
355
|
+
* ]
|
|
356
|
+
* ```
|
|
357
|
+
*
|
|
358
|
+
* A raw SQL string is also accepted, as an escape hatch for what the
|
|
359
|
+
* structured form cannot express. A string gets no column-name checking: a
|
|
360
|
+
* mistake in it shows up only when Postgres runs `CREATE POLICY` at deploy.
|
|
361
|
+
*/
|
|
119
362
|
using(expr: string | PolicyExprRef): this;
|
|
120
363
|
/**
|
|
121
364
|
* "Rows of THIS table whose owner the caller is a member of" — the membership
|
|
@@ -145,6 +388,12 @@ export declare class PolicyBuilder {
|
|
|
145
388
|
column?: string;
|
|
146
389
|
userColumn?: string;
|
|
147
390
|
}): this;
|
|
391
|
+
/**
|
|
392
|
+
* The `WITH CHECK (...)` expression: rows written by an `INSERT` or `UPDATE`
|
|
393
|
+
* must satisfy it, or the write is rejected. Takes the same two forms as
|
|
394
|
+
* `using()`: a structured expression (preferred, column names checked at
|
|
395
|
+
* compile time) or a raw SQL string.
|
|
396
|
+
*/
|
|
148
397
|
withCheck(expr: string | PolicyExprRef): this;
|
|
149
398
|
/**
|
|
150
399
|
* The columns this policy reads get no index of their own from it (Palbase
|
|
@@ -166,10 +415,50 @@ export declare class PolicyBuilder {
|
|
|
166
415
|
* `(table, name)`, so names must be unique per table.
|
|
167
416
|
*/
|
|
168
417
|
export declare function policy(name: string): PolicyBuilder;
|
|
418
|
+
/**
|
|
419
|
+
* A named CHECK constraint on a table: its name and its SQL expression.
|
|
420
|
+
*
|
|
421
|
+
* `check()` returns one, with the typed expression already compiled to SQL. A
|
|
422
|
+
* table's `checks` may also be a plain array of these, with `expr` written as
|
|
423
|
+
* raw SQL. Both forms end up the same. A deploy matches CHECKs by name and
|
|
424
|
+
* compares `expr` as written (ignoring leading and trailing spaces): the same
|
|
425
|
+
* text is left alone, and changed text replaces the constraint — `n > 0` and
|
|
426
|
+
* `(n > 0)` count as a change.
|
|
427
|
+
*/
|
|
169
428
|
export interface CheckDef {
|
|
170
429
|
name: string;
|
|
430
|
+
/** The SQL expression. `check()` compiles it for you; in a plain `checks` array you write it yourself. */
|
|
171
431
|
expr: string;
|
|
172
432
|
}
|
|
433
|
+
/**
|
|
434
|
+
* `check("<name>", <expression>)`: a typed CHECK constraint.
|
|
435
|
+
*
|
|
436
|
+
* The expression is written with the same builder as a policy expression; this
|
|
437
|
+
* DSL has one expression language, not two. Column names come from the context
|
|
438
|
+
* the table's `checks` callback receives, so a misspelled name does not
|
|
439
|
+
* compile. With a raw SQL string, the same mistake shows up only when Postgres
|
|
440
|
+
* runs `ALTER TABLE ... ADD CONSTRAINT` at deploy.
|
|
441
|
+
*
|
|
442
|
+
* A CHECK may only read the current row's columns: `existsIn(...)`,
|
|
443
|
+
* `auth.uid()` and the `in` operator are rejected with an error when the table
|
|
444
|
+
* is declared.
|
|
445
|
+
*
|
|
446
|
+
* ```ts
|
|
447
|
+
* defineTable("payments", {
|
|
448
|
+
* columns: { amount: integer().notNull(), currency: text().notNull() },
|
|
449
|
+
* checks: (c) => [
|
|
450
|
+
* check("payments_amount_pos", c.col("amount").gt(0)),
|
|
451
|
+
* check("payments_currency_set", c.col("currency").isNull(true)),
|
|
452
|
+
* ],
|
|
453
|
+
* })
|
|
454
|
+
* ```
|
|
455
|
+
*
|
|
456
|
+
* @param name The constraint name; it must not be empty. Palbase matches CHECK
|
|
457
|
+
* constraints by name (a changed expression means DROP and ADD),
|
|
458
|
+
* so the name must be unique per table.
|
|
459
|
+
* @param expr The condition every row must satisfy.
|
|
460
|
+
* @returns The constraint, with its expression compiled to SQL.
|
|
461
|
+
*/
|
|
173
462
|
export declare function check(name: string, expr: PolicyExprRef): CheckDef;
|
|
174
463
|
/**
|
|
175
464
|
* The SQL of a policy expression: a raw string as it is, a structure rendered.
|
|
@@ -1,3 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A named raw-SQL DDL object declared in a schema file (`db/public.ts`,
|
|
3
|
+
* `db/billing.ts`, …) for what the typed DSL cannot express: an index method
|
|
4
|
+
* other than btree (`gin`, `gist`, `brin`, `hash`), an opclass, `EXCLUDE`
|
|
5
|
+
* constraints, views, and PL/pgSQL functions.
|
|
6
|
+
*
|
|
7
|
+
* REACH FOR THE TYPED FIELD FIRST. These are declarative and should not come
|
|
8
|
+
* through here:
|
|
9
|
+
* - a CHECK constraint → `checks: [{ name, expr }]`, or the typed
|
|
10
|
+
* `checks: (c) => [check(name, c.col("n").gt(0))]`
|
|
11
|
+
* - a partial index → `index(n).on(…).where(…)`
|
|
12
|
+
* - an expression index → `index(n).onExpression("lower(email)")`
|
|
13
|
+
* - a covering index → `index(n).on(…).include([…])`
|
|
14
|
+
* - a UNIQUE or PARTIAL UNIQUE index → `index(n).unique().where(…)`
|
|
15
|
+
* - a composite FOREIGN KEY → `foreignKeys: (c) => [foreignKey(n).on(…).references(…)]`
|
|
16
|
+
* Those are diffed, validated against the live database before the push, and
|
|
17
|
+
* their mistakes answer you where you write them. A `raw()` is none of that.
|
|
18
|
+
*
|
|
19
|
+
* The deploy runs `up` verbatim on the privileged DDL connection — the same
|
|
20
|
+
* trust as `policy().using()`. It is matched by `name` across deploys:
|
|
21
|
+
* - the same text (leading and trailing spaces aside) → nothing happens;
|
|
22
|
+
* - an index whose text changes under the same name → refused: an index is
|
|
23
|
+
* built `CONCURRENTLY` next to the one serving, so give the new one its own
|
|
24
|
+
* name, and the old one goes at complete;
|
|
25
|
+
* - anything else whose text changes → applied again (after its old `drop`,
|
|
26
|
+
* unless the new text says `CREATE OR REPLACE`).
|
|
27
|
+
* A raw index removed from the schema is dropped at complete.
|
|
28
|
+
*
|
|
29
|
+
* There is no teardown option on the TABLE-level form: a `drop` belongs to a
|
|
30
|
+
* schema-level unit (`RawUnitDef`, below), whose deploy runs it, and
|
|
31
|
+
* `defineTable` refuses a table-level `raw()` that carries one. A trigger, a
|
|
32
|
+
* rule or a view needs a `drop` — a rollback has to be able to take it back —
|
|
33
|
+
* so declare it as a schema-level unit.
|
|
34
|
+
*/
|
|
1
35
|
export interface RawConstraintDef {
|
|
2
36
|
name: string;
|
|
3
37
|
up: string;
|
|
@@ -36,6 +70,28 @@ export interface RawUnitDef extends RawConstraintDef {
|
|
|
36
70
|
*/
|
|
37
71
|
before?: boolean;
|
|
38
72
|
}
|
|
73
|
+
/**
|
|
74
|
+
* Declares a named raw-SQL unit, for what the typed DSL cannot express.
|
|
75
|
+
*
|
|
76
|
+
* With two arguments it is the table-level form ({@link RawConstraintDef}),
|
|
77
|
+
* for `defineTable`'s `raw: [...]`. With an `options` argument it is the
|
|
78
|
+
* schema-level form ({@link RawUnitDef}), for
|
|
79
|
+
* `defineSchema(name, { tables, raw: [...] })`: `drop` is the SQL that tears
|
|
80
|
+
* the unit down, and `before: true` makes it before the schema's tables.
|
|
81
|
+
*
|
|
82
|
+
* @param name The unit's name. It identifies the unit from one deploy to the
|
|
83
|
+
* next, so two units in one schema cannot share it.
|
|
84
|
+
* @param up The SQL the deploy runs, verbatim, to create the unit.
|
|
85
|
+
* @param options Schema-level units only: `drop` and `before`.
|
|
86
|
+
* @returns The unit, to place in a `raw: [...]` list.
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* ```ts
|
|
90
|
+
* raw("monthly_totals",
|
|
91
|
+
* `CREATE MATERIALIZED VIEW public.monthly_totals AS SELECT … FROM public.entries GROUP BY …`,
|
|
92
|
+
* { drop: "DROP MATERIALIZED VIEW public.monthly_totals" })
|
|
93
|
+
* ```
|
|
94
|
+
*/
|
|
39
95
|
export declare function raw(name: string, up: string): RawConstraintDef;
|
|
40
96
|
export declare function raw(name: string, up: string, options: {
|
|
41
97
|
drop?: string;
|