palgres 0.0.0-stage → 0.2.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/LICENSE +27 -0
- package/README.md +2 -2
- package/dist/chunks/JJVO5DTA.js +2 -0
- package/dist/chunks/JZLQMXDV.js +2 -0
- package/dist/chunks/XZNL22V6.js +5 -0
- package/dist/dsl.js +2 -0
- package/dist/index.js +372 -0
- package/dist/testing.js +2 -0
- package/package.json +32 -4
- package/types/internal/src/data.d.ts +15 -0
- package/types/palgres/src/adopt.d.ts +17 -0
- package/types/palgres/src/connect.d.ts +37 -0
- package/types/palgres/src/dsl/columns.d.ts +388 -0
- package/types/palgres/src/dsl/foreign-key.d.ts +33 -0
- package/types/palgres/src/dsl/index.d.ts +11 -0
- package/types/palgres/src/dsl/policy.d.ts +181 -0
- package/types/palgres/src/dsl/raw.d.ts +43 -0
- package/types/palgres/src/dsl/schema.d.ts +288 -0
- package/types/palgres/src/dsl/to-model.d.ts +6 -0
- package/types/palgres/src/forget.d.ts +12 -0
- package/types/palgres/src/index.d.ts +15 -0
- package/types/palgres/src/introspect.d.ts +17 -0
- package/types/palgres/src/migrate.d.ts +189 -0
- package/types/palgres/src/model.d.ts +181 -0
- package/types/palgres/src/plan.d.ts +107 -0
- package/types/palgres/src/pull.d.ts +13 -0
- package/types/palgres/src/query/admission.d.ts +80 -0
- package/types/palgres/src/query/catalog.d.ts +50 -0
- package/types/palgres/src/query/client.d.ts +154 -0
- package/types/palgres/src/query/data-mark.d.ts +7 -0
- package/types/palgres/src/query/diagnostics.d.ts +76 -0
- package/types/palgres/src/query/driver.d.ts +299 -0
- package/types/palgres/src/query/env-gen.d.ts +14 -0
- package/types/palgres/src/query/errors.d.ts +168 -0
- package/types/palgres/src/query/guards.d.ts +62 -0
- package/types/palgres/src/query/index.d.ts +76 -0
- package/types/palgres/src/query/options.d.ts +75 -0
- package/types/palgres/src/query/relations.d.ts +15 -0
- package/types/palgres/src/query/repository.d.ts +42 -0
- package/types/palgres/src/query/request.d.ts +54 -0
- package/types/palgres/src/query/search.d.ts +55 -0
- package/types/palgres/src/query/surface.d.ts +9 -0
- package/types/palgres/src/query/tx-plan.d.ts +229 -0
- package/types/palgres/src/query/typed.d.ts +537 -0
- package/types/palgres/src/query/unique.d.ts +16 -0
- package/types/palgres/src/search-worker.d.ts +36 -0
- package/types/palgres/src/search.d.ts +107 -0
- package/types/palgres/src/testing/fake.d.ts +22 -0
- package/types/palgres/src/testing/index.d.ts +2 -0
- package/types/palgres/src/units.d.ts +24 -0
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { type ColumnMap, type TableHandle } from './schema.js';
|
|
2
|
+
/**
|
|
3
|
+
* policy.ts — the RLS policy authoring DSL.
|
|
4
|
+
*
|
|
5
|
+
* `policy(name)` returns a fluent builder that mirrors the `ColumnBuilder`
|
|
6
|
+
* style in columns.ts: each chainable method mutates the underlying
|
|
7
|
+
* definition and returns the builder so calls compose. The terminal value is
|
|
8
|
+
* a plain {@link PolicyDef}; policySQL() says its expressions in SQL.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* import { policy } from "palbase/backend";
|
|
12
|
+
*
|
|
13
|
+
* policy("owner_select")
|
|
14
|
+
* .for("select")
|
|
15
|
+
* .to("authenticated")
|
|
16
|
+
* .using("owner = (select auth.uid())");
|
|
17
|
+
*/
|
|
18
|
+
export type PolicyExpr = {
|
|
19
|
+
kind: "col";
|
|
20
|
+
name: string;
|
|
21
|
+
alias?: string;
|
|
22
|
+
} | {
|
|
23
|
+
kind: "authUid";
|
|
24
|
+
} | {
|
|
25
|
+
kind: "lit";
|
|
26
|
+
value: string | number | boolean | null;
|
|
27
|
+
} | {
|
|
28
|
+
kind: "binop";
|
|
29
|
+
op: PolicyBinOp;
|
|
30
|
+
left: PolicyExpr;
|
|
31
|
+
right: PolicyExpr;
|
|
32
|
+
} | {
|
|
33
|
+
kind: "and";
|
|
34
|
+
parts: PolicyExpr[];
|
|
35
|
+
} | {
|
|
36
|
+
kind: "or";
|
|
37
|
+
parts: PolicyExpr[];
|
|
38
|
+
} | {
|
|
39
|
+
kind: "not";
|
|
40
|
+
expr: PolicyExpr;
|
|
41
|
+
} | {
|
|
42
|
+
kind: "isNull";
|
|
43
|
+
expr: PolicyExpr;
|
|
44
|
+
negated: boolean;
|
|
45
|
+
} | {
|
|
46
|
+
kind: "exists";
|
|
47
|
+
table: string;
|
|
48
|
+
alias: string;
|
|
49
|
+
outer: string;
|
|
50
|
+
predicate: PolicyExpr;
|
|
51
|
+
};
|
|
52
|
+
export type PolicyBinOp = "eq" | "neq" | "gt" | "gte" | "lt" | "lte" | "in";
|
|
53
|
+
export declare class PolicyExprRef {
|
|
54
|
+
readonly expr: PolicyExpr;
|
|
55
|
+
constructor(expr: PolicyExpr);
|
|
56
|
+
private bin;
|
|
57
|
+
eq(other: PolicyOperand): PolicyExprRef;
|
|
58
|
+
neq(other: PolicyOperand): PolicyExprRef;
|
|
59
|
+
gt(other: PolicyOperand): PolicyExprRef;
|
|
60
|
+
gte(other: PolicyOperand): PolicyExprRef;
|
|
61
|
+
lt(other: PolicyOperand): PolicyExprRef;
|
|
62
|
+
lte(other: PolicyOperand): PolicyExprRef;
|
|
63
|
+
isNull(negated?: boolean): PolicyExprRef;
|
|
64
|
+
and(...others: PolicyExprRef[]): PolicyExprRef;
|
|
65
|
+
or(...others: PolicyExprRef[]): PolicyExprRef;
|
|
66
|
+
not(): PolicyExprRef;
|
|
67
|
+
}
|
|
68
|
+
export type TableRef<Cols extends string, N extends string = string> = TableHandle<Record<Cols, ColumnMap[string]>, N>;
|
|
69
|
+
export type PolicyOperand = PolicyExprRef | string | number | boolean | null;
|
|
70
|
+
export interface PolicyExprCtx<Cols extends string = string> {
|
|
71
|
+
/** Bu tablonun bir kolonu. Bilinmeyen ad DERLENMEZ. */
|
|
72
|
+
col(name: Cols): PolicyExprRef;
|
|
73
|
+
existsIn<C extends ColumnMap, N extends string>(table: TableHandle<C, N>, predicate: (row: PolicyExprCtx<Extract<keyof C, string>>) => PolicyExprRef): PolicyExprRef;
|
|
74
|
+
auth: {
|
|
75
|
+
uid(): PolicyExprRef;
|
|
76
|
+
};
|
|
77
|
+
lit(v: string | number | boolean | null): PolicyExprRef;
|
|
78
|
+
and(...parts: PolicyExprRef[]): PolicyExprRef;
|
|
79
|
+
or(...parts: PolicyExprRef[]): PolicyExprRef;
|
|
80
|
+
not(part: PolicyExprRef): PolicyExprRef;
|
|
81
|
+
}
|
|
82
|
+
export declare function exprCtx<Cols extends string = string>(_columns?: readonly Cols[], depth?: number,
|
|
83
|
+
ownTable?: string): PolicyExprCtx<Cols>;
|
|
84
|
+
/** The SQL command a policy applies to. `"all"` covers SELECT/INSERT/UPDATE/DELETE. */
|
|
85
|
+
export type PolicyCommand = "all" | "select" | "insert" | "update" | "delete";
|
|
86
|
+
/** Whether a policy is permissive (OR-combined, the default) or restrictive
|
|
87
|
+
* (AND-combined). Mirrors Postgres `CREATE POLICY ... AS PERMISSIVE|RESTRICTIVE`. */
|
|
88
|
+
export type PolicyMode = "permissive" | "restrictive";
|
|
89
|
+
export interface PolicyDef {
|
|
90
|
+
name: string;
|
|
91
|
+
command: PolicyCommand;
|
|
92
|
+
roles: string[];
|
|
93
|
+
using: string | PolicyExpr | null;
|
|
94
|
+
withCheck: string | PolicyExpr | null;
|
|
95
|
+
permissive: boolean;
|
|
96
|
+
/** false: the columns it reads get no index of their own from it (withoutIndexes()). */
|
|
97
|
+
indexes?: false;
|
|
98
|
+
}
|
|
99
|
+
export type PolicyRole = "anon" | "authenticated" | "service_role" | "backend_anon" | "backend_authenticated" | "backend_service_role"
|
|
100
|
+
/** Every role (Postgres' PUBLIC): a database taken over may have a policy for it (palgres pull writes it). */
|
|
101
|
+
| "public"
|
|
102
|
+
/** A database role by its own name: what a database taken over has a policy for (palgres pull writes it). */
|
|
103
|
+
| `role:${string}`;
|
|
104
|
+
export declare class PolicyBuilder {
|
|
105
|
+
readonly _def: PolicyDef;
|
|
106
|
+
constructor(name: string);
|
|
107
|
+
/** Restrict the policy to a single SQL command (default `"all"`). */
|
|
108
|
+
for(command: PolicyCommand): this;
|
|
109
|
+
/**
|
|
110
|
+
* Set the DB roles the policy applies to (the `TO` clause), replacing any
|
|
111
|
+
* previously-set roles. Call with no arguments to target PUBLIC (all roles).
|
|
112
|
+
*
|
|
113
|
+
* @example
|
|
114
|
+
* policy("p").to("authenticated")
|
|
115
|
+
* policy("p").to("authenticated", "service_role")
|
|
116
|
+
* policy("p").to() // PUBLIC
|
|
117
|
+
*/
|
|
118
|
+
to(...roles: PolicyRole[]): this;
|
|
119
|
+
using(expr: string | PolicyExprRef): this;
|
|
120
|
+
/**
|
|
121
|
+
* "Rows of THIS table whose owner the caller is a member of" — the membership
|
|
122
|
+
* pattern, written so it cannot recurse.
|
|
123
|
+
*
|
|
124
|
+
* THE TRAP IT EXISTS FOR. Written by hand, membership policies point at each
|
|
125
|
+
* other: `channels` is visible to members, so its policy reads
|
|
126
|
+
* `channel_members`; `channel_members` is visible to members, so its policy
|
|
127
|
+
* reads `channels`. Postgres refuses the pair at query time with `infinite
|
|
128
|
+
* recursion detected in policy for relation ...`, and the error names the
|
|
129
|
+
* relation but not the cycle. The way out is asymmetry — the MEMBERSHIP table
|
|
130
|
+
* is protected by `user_id = auth.uid()` and nothing else, and every other
|
|
131
|
+
* table subqueries INTO it. That shape was in the platform's own schema and
|
|
132
|
+
* written down nowhere; a customer recovered it by reading that schema.
|
|
133
|
+
*
|
|
134
|
+
* `(select auth.uid())` rather than a bare call: the scalar subquery is
|
|
135
|
+
* evaluated ONCE per statement instead of per row.
|
|
136
|
+
*
|
|
137
|
+
* @example
|
|
138
|
+
* // channels: visible to members. The membership table gets the simple one.
|
|
139
|
+
* policy("member_read").for("select").to("authenticated")
|
|
140
|
+
* .memberOf("channel_members", "channel_id")
|
|
141
|
+
* // → id IN (SELECT "channel_id" FROM "channel_members"
|
|
142
|
+
* // WHERE "user_id" = (select auth.uid()))
|
|
143
|
+
*/
|
|
144
|
+
memberOf(membershipTable: string, foreignKey: string, options?: {
|
|
145
|
+
column?: string;
|
|
146
|
+
userColumn?: string;
|
|
147
|
+
}): this;
|
|
148
|
+
withCheck(expr: string | PolicyExprRef): this;
|
|
149
|
+
/**
|
|
150
|
+
* The columns this policy reads get no index of their own from it (Palbase
|
|
151
|
+
* indexes them unasked, `<table>_<column>_idx`). For a database taken over
|
|
152
|
+
* whose table has none: `palgres pull` writes it, so the take-over builds
|
|
153
|
+
* nothing nobody asked for.
|
|
154
|
+
*/
|
|
155
|
+
withoutIndexes(): this;
|
|
156
|
+
/** Set the policy mode: `"permissive"` (default, OR-combined) or
|
|
157
|
+
* `"restrictive"` (AND-combined). */
|
|
158
|
+
as(mode: PolicyMode): this;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Start authoring an RLS policy. Returns a {@link PolicyBuilder}; the resulting
|
|
162
|
+
* `PolicyBuilder` is accepted directly in a table's `policies: [...]` array
|
|
163
|
+
* (its `_def` is read at schema-extract time).
|
|
164
|
+
*
|
|
165
|
+
* @param name The policy name. Palbase reconciliation keys policies by
|
|
166
|
+
* `(table, name)`, so names must be unique per table.
|
|
167
|
+
*/
|
|
168
|
+
export declare function policy(name: string): PolicyBuilder;
|
|
169
|
+
export interface CheckDef {
|
|
170
|
+
name: string;
|
|
171
|
+
expr: string;
|
|
172
|
+
}
|
|
173
|
+
export declare function check(name: string, expr: PolicyExprRef): CheckDef;
|
|
174
|
+
/**
|
|
175
|
+
* The SQL of a policy expression: a raw string as it is, a structure rendered.
|
|
176
|
+
* `auth.uid()` is `(SELECT auth.uid())` — evaluated once per statement, not
|
|
177
|
+
* per row. Inside `existsIn` a column without an alias is the POLICY's own
|
|
178
|
+
* table's, written with that table's name: left bare, Postgres would read it
|
|
179
|
+
* as the inner table's and the policy would filter nothing.
|
|
180
|
+
*/
|
|
181
|
+
export declare function policySQL(expr: string | PolicyExpr | null): string | undefined;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export interface RawConstraintDef {
|
|
2
|
+
name: string;
|
|
3
|
+
up: string;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* A SCHEMA-LEVEL named raw unit — `defineSchema(name, { tables, raw: [...] })` —
|
|
7
|
+
* for what belongs to no single table: a view, a materialized view, a function
|
|
8
|
+
* that reads several tables.
|
|
9
|
+
*
|
|
10
|
+
* Unlike the table-level form it HAS a teardown, and the deploy RUNS it:
|
|
11
|
+
* - a new unit runs `up`;
|
|
12
|
+
* - a unit whose `up` changed first runs the `drop` recorded with its PREVIOUS
|
|
13
|
+
* body, then the new `up` — in one transaction;
|
|
14
|
+
* - a unit removed from the declaration runs its recorded `drop`. A unit with
|
|
15
|
+
* no `drop` cannot be removed: the push is refused until one is added.
|
|
16
|
+
*
|
|
17
|
+
* A body may not create a TABLE — tables are always declared with
|
|
18
|
+
* `defineTable()` (use `control: "external"` for one something else manages).
|
|
19
|
+
* A view must say `WITH (security_invoker = true)`. A materialized view has no
|
|
20
|
+
* row-level security, so the platform lets only the service roles read it:
|
|
21
|
+
* reach it from backend code with `$asService()`.
|
|
22
|
+
*
|
|
23
|
+
* ```ts
|
|
24
|
+
* raw("monthly_totals",
|
|
25
|
+
* `CREATE MATERIALIZED VIEW public.monthly_totals AS SELECT … FROM public.entries GROUP BY …`,
|
|
26
|
+
* { drop: "DROP MATERIALIZED VIEW public.monthly_totals" })
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export interface RawUnitDef extends RawConstraintDef {
|
|
30
|
+
drop?: string;
|
|
31
|
+
/**
|
|
32
|
+
* Made before the schema's tables, not after: what a column's type or
|
|
33
|
+
* default needs — a domain, a composite type, a sequence, a function a
|
|
34
|
+
* default calls (`palgres pull` writes these so). Function bodies are not
|
|
35
|
+
* checked while it is made (check_function_bodies off, as pg_dump does).
|
|
36
|
+
*/
|
|
37
|
+
before?: boolean;
|
|
38
|
+
}
|
|
39
|
+
export declare function raw(name: string, up: string): RawConstraintDef;
|
|
40
|
+
export declare function raw(name: string, up: string, options: {
|
|
41
|
+
drop?: string;
|
|
42
|
+
before?: boolean;
|
|
43
|
+
}): RawUnitDef;
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import type { ColumnBuilder } from './columns.js';
|
|
2
|
+
import { ForeignKeyBuilder, foreignKey, toForeignKeyDef, type ForeignKeyDef } from './foreign-key.js';
|
|
3
|
+
import { PolicyBuilder, check, type CheckDef, type PolicyDef, type PolicyExpr, type PolicyExprCtx, type PolicyExprRef } from './policy.js';
|
|
4
|
+
import type { RawConstraintDef, RawUnitDef } from './raw.js';
|
|
5
|
+
import { type Search, type SearchMetric } from '../model.js';
|
|
6
|
+
/** A table's key: bare in public, "schema.table" in any other. */
|
|
7
|
+
export declare function qualifiedTableKey(schemaName: string, tableName: string): string;
|
|
8
|
+
/**
|
|
9
|
+
* A map of column builders keyed by column name — the value you write under
|
|
10
|
+
* the `columns` key of `defineTable("<name>", { columns })`.
|
|
11
|
+
*
|
|
12
|
+
* The default `Record<string, ColumnBuilder>` keeps bare references compiling
|
|
13
|
+
* without a type argument.
|
|
14
|
+
*/
|
|
15
|
+
export type ColumnMap = Record<string, ColumnBuilder>;
|
|
16
|
+
/**
|
|
17
|
+
* The author-facing value written under each table key:
|
|
18
|
+
* `{ columns, rls?, policies? }`.
|
|
19
|
+
*
|
|
20
|
+
* - `columns`: the column map (required).
|
|
21
|
+
* - `rls`: enable + FORCE row-level security on this table. **Defaults to
|
|
22
|
+
* `true`**, and is forced on when `policies` is non-empty. A table with RLS
|
|
23
|
+
* and no policies is deny-all, which is the starting state: nothing reads it
|
|
24
|
+
* until a policy says who may. Set `rls: false` only for a genuinely public
|
|
25
|
+
* table — it is an explicit opt-out that a reviewer can grep for, not
|
|
26
|
+
* something you get by forgetting.
|
|
27
|
+
* - `policies`: the RLS policies for this table, authored with `policy(name)`.
|
|
28
|
+
* Each entry may be a {@link PolicyBuilder} (the normal `policy(...)` chain)
|
|
29
|
+
* or a raw {@link PolicyDef} object.
|
|
30
|
+
*
|
|
31
|
+
* The `C` type parameter preserves the precise per-column phantom types so the
|
|
32
|
+
* typed `Database.public.*` surface keeps inferring insert/row shapes.
|
|
33
|
+
*/
|
|
34
|
+
export interface IndexDef {
|
|
35
|
+
name: string;
|
|
36
|
+
unique?: boolean;
|
|
37
|
+
columns?: string[];
|
|
38
|
+
expression?: string;
|
|
39
|
+
where?: PolicyExpr;
|
|
40
|
+
sort?: "asc" | "desc";
|
|
41
|
+
nulls?: "first" | "last";
|
|
42
|
+
include?: string[];
|
|
43
|
+
}
|
|
44
|
+
export declare class IndexBuilder {
|
|
45
|
+
readonly _def: IndexDef;
|
|
46
|
+
constructor(name: string);
|
|
47
|
+
unique(): this;
|
|
48
|
+
on(...cols: PolicyExprRef[]): this;
|
|
49
|
+
onExpression(expr: string): this;
|
|
50
|
+
where(expr: PolicyExprRef): this;
|
|
51
|
+
desc(): this;
|
|
52
|
+
asc(): this;
|
|
53
|
+
nulls(where: "first" | "last"): this;
|
|
54
|
+
include(cols: string[]): this;
|
|
55
|
+
}
|
|
56
|
+
export declare function index(name: string): IndexBuilder;
|
|
57
|
+
/**
|
|
58
|
+
* A table's rows found by meaning and by word — `Database.public.<table>.search("…")`.
|
|
59
|
+
*
|
|
60
|
+
* ```ts
|
|
61
|
+
* search: { from: ["title", "body"], model: "default" }
|
|
62
|
+
* ```
|
|
63
|
+
*
|
|
64
|
+
* The platform embeds each row's `from` columns when the row is written and
|
|
65
|
+
* when one of them changes; the app writes no embedding code. Changing
|
|
66
|
+
* `model`, `dimensions`, `metric` or `from` embeds every row again in the
|
|
67
|
+
* background, and search goes on answering from the vectors it had until the
|
|
68
|
+
* new ones are complete.
|
|
69
|
+
*/
|
|
70
|
+
export interface SearchInput<Col extends string = string> {
|
|
71
|
+
/** The text columns (text, varchar, citext) a row is found by; their text is joined in this order. */
|
|
72
|
+
from: readonly Col[];
|
|
73
|
+
/** The embedding model, as the embedder names it — the platform's: "default", or a catalogue model as provider:model. */
|
|
74
|
+
model: string;
|
|
75
|
+
/** The vectors' dimensions (default 1536; at most 4000 — past 2000 they are kept in half precision). */
|
|
76
|
+
dimensions?: number;
|
|
77
|
+
/** How two vectors are compared (default "cosine"). */
|
|
78
|
+
metric?: SearchMetric;
|
|
79
|
+
/**
|
|
80
|
+
* A row whose text changed, until it is embedded again: "null" (default) —
|
|
81
|
+
* not found by its old meaning, only by its words; "keep" — still found by
|
|
82
|
+
* its old meaning.
|
|
83
|
+
*/
|
|
84
|
+
staleness?: 'null' | 'keep';
|
|
85
|
+
}
|
|
86
|
+
export interface TableInput<C extends ColumnMap = ColumnMap> {
|
|
87
|
+
columns: C;
|
|
88
|
+
rls?: boolean;
|
|
89
|
+
policies?: (p: PolicyExprCtx<Extract<keyof C, string>>) => (PolicyBuilder | PolicyDef)[];
|
|
90
|
+
ignored?: boolean;
|
|
91
|
+
/** Composite/named primary key (ordered column names). Omit for single-column inline .primaryKey(). */
|
|
92
|
+
primaryKey?: string[];
|
|
93
|
+
/** Named multi-column UNIQUE constraints. */
|
|
94
|
+
unique?: {
|
|
95
|
+
name: string;
|
|
96
|
+
columns: string[];
|
|
97
|
+
}[];
|
|
98
|
+
/** Named raw-SQL DDL objects (EXCLUDE, triggers, views) that the typed DSL cannot express. */
|
|
99
|
+
raw?: RawConstraintDef[];
|
|
100
|
+
checks?: CheckDef[] | ((c: PolicyExprCtx<Extract<keyof C, string>>) => CheckDef[]);
|
|
101
|
+
/**
|
|
102
|
+
* Plain (non-unique) btree indexes over an ordered column list, emitted as
|
|
103
|
+
* standalone `CREATE INDEX [IF NOT EXISTS] name ON table (col1, col2)`
|
|
104
|
+
* statements (NOT a table clause — a separate migration statement category).
|
|
105
|
+
* Structural compare by NAME (no expression normalization). `name` and each
|
|
106
|
+
* column are identifier-validated by the Go differ.
|
|
107
|
+
*
|
|
108
|
+
* NAME is the WHOLE diff key. Editing `.where()`, `.onExpression()`,
|
|
109
|
+
* `.include()` or the sort under a name that is already live does NOTHING —
|
|
110
|
+
* the deploy matches the name, sees the index, and skips. Give a changed
|
|
111
|
+
* definition a new name.
|
|
112
|
+
*
|
|
113
|
+
* Scope, via `index(name)`: `.on(...cols)` · `.onExpression(sql)` (expression
|
|
114
|
+
* index) · `.where(pred)` (partial index, typed predicate) · `.unique()`
|
|
115
|
+
* (UNIQUE, and WITH `.where()` a PARTIAL UNIQUE index) · `.include(cols)`
|
|
116
|
+
* (covering) · `.asc()`/`.desc()`/`.nulls()`. Predicates and expressions are
|
|
117
|
+
* probed against the live database before the push — as a prepared statement
|
|
118
|
+
* AND as a real CREATE INDEX on an empty `LIKE` clone, which is what catches
|
|
119
|
+
* a subquery predicate or a non-IMMUTABLE function before the apply.
|
|
120
|
+
*
|
|
121
|
+
* `index(n).unique().where(p)` is the ONE declarative way to say "NULLs are
|
|
122
|
+
* free, non-NULLs are unique" — the table-level `unique:` cannot carry a
|
|
123
|
+
* predicate, because it renders a CONSTRAINT and PostgreSQL has no partial
|
|
124
|
+
* unique CONSTRAINT (only a partial unique INDEX).
|
|
125
|
+
*
|
|
126
|
+
* NOT expressible here: another index method (gin/gist/brin/hash), an
|
|
127
|
+
* opclass, or CONCURRENTLY.
|
|
128
|
+
*/
|
|
129
|
+
indexes?: (p: PolicyExprCtx<Extract<keyof C, string>>) => (IndexBuilder | IndexDef)[];
|
|
130
|
+
foreignKeys?: (p: PolicyExprCtx<Extract<keyof C, string>>) => (ForeignKeyBuilder | ForeignKeyDef)[];
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* A table definition: its name, its columns and what else it declares, as
|
|
134
|
+
* toModel() and the typed queries read it.
|
|
135
|
+
*
|
|
136
|
+
* `defineSchema` derives `name` from the object key, so authors never repeat
|
|
137
|
+
* the table name. `rls`/`policies` are always present after normalization
|
|
138
|
+
* (defaulted to `true`/`[]`).
|
|
139
|
+
*
|
|
140
|
+
* The `C` type parameter preserves the precise per-column phantom types so that
|
|
141
|
+
* downstream mapped types (InsertShape, RowShape) can discriminate on them.
|
|
142
|
+
*/
|
|
143
|
+
export interface TableDef<C extends ColumnMap = ColumnMap> {
|
|
144
|
+
name: string;
|
|
145
|
+
columns: C;
|
|
146
|
+
rls: boolean;
|
|
147
|
+
ignored?: boolean;
|
|
148
|
+
policies: PolicyDef[];
|
|
149
|
+
primaryKey?: string[];
|
|
150
|
+
unique?: {
|
|
151
|
+
name: string;
|
|
152
|
+
columns: string[];
|
|
153
|
+
}[];
|
|
154
|
+
/** Named raw-SQL DDL objects emitted verbatim on deploy. Tracked by name. */
|
|
155
|
+
raw?: RawConstraintDef[];
|
|
156
|
+
checks?: CheckDef[];
|
|
157
|
+
indexes?: IndexDef[];
|
|
158
|
+
foreignKeys?: ForeignKeyDef[];
|
|
159
|
+
/** The table's search, its defaults said — see SearchInput. */
|
|
160
|
+
search?: Search;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* A schema definition containing multiple tables, keyed by table name.
|
|
164
|
+
*
|
|
165
|
+
* The `T` type parameter preserves the exact `TableDef<...>` type for each
|
|
166
|
+
* table so that `SchemaDef["tables"]["rooms"]` resolves to the precise
|
|
167
|
+
* `TableDef<{ id: ColumnBuilder<'uuid', false, true, never>; ... }>`.
|
|
168
|
+
*/
|
|
169
|
+
export interface SchemaDef<T extends Record<string, TableDef> = Record<string, TableDef>> {
|
|
170
|
+
/** The schema's own name — `public`, `billing`, … Declared, never derived. */
|
|
171
|
+
name: string;
|
|
172
|
+
/** Kept for the schema files v2 wrote; v3 has no REST surface over tables, so it changes nothing. */
|
|
173
|
+
exposed: boolean;
|
|
174
|
+
tables: T;
|
|
175
|
+
/** Postgres extensions the deploy creates (CREATE EXTENSION IF NOT EXISTS). */
|
|
176
|
+
extensions: string[];
|
|
177
|
+
/** Schema-level named raw units (views, materialized views, functions) — see
|
|
178
|
+
* `RawUnitDef`. `defineSchema` always sets it (`[]` when none); OPTIONAL so a
|
|
179
|
+
* hand-built `SchemaDef` from before the field still compiles. */
|
|
180
|
+
raw?: RawUnitDef[];
|
|
181
|
+
}
|
|
182
|
+
/** The author-facing input to `defineSchema` — a `tables` map whose keys are
|
|
183
|
+
* the table names and whose values are `{ columns, rls?, policies? }`, plus an
|
|
184
|
+
* optional `extensions` allowlist. */
|
|
185
|
+
export interface SchemaInput<T extends Record<string, TableInput> = Record<string, TableInput>> {
|
|
186
|
+
tables: T;
|
|
187
|
+
/**
|
|
188
|
+
* Postgres extensions the deploy creates, e.g. `["pg_trgm"]` — those the
|
|
189
|
+
* database machine allows.
|
|
190
|
+
*/
|
|
191
|
+
extensions?: string[];
|
|
192
|
+
}
|
|
193
|
+
/** Map the author's `{ tables: { <name>: { columns } } }` input to the
|
|
194
|
+
* `{ tables: { <name>: TableDef<columns> } }` runtime/type shape, threading the
|
|
195
|
+
* per-table column map `T[K]["columns"]` so column-level inference survives. */
|
|
196
|
+
type TablesByName<T extends readonly TableHandle[]> = {
|
|
197
|
+
[K in T[number] as K[typeof TABLE_META]["name"]]: K[typeof TABLE_META];
|
|
198
|
+
};
|
|
199
|
+
/**
|
|
200
|
+
* How a schema file reads. One file per schema — `db/public.ts`, `db/billing.ts`
|
|
201
|
+
* — and the schema says its own name:
|
|
202
|
+
*
|
|
203
|
+
* const todos = defineTable("todos", {
|
|
204
|
+
* columns: {
|
|
205
|
+
* id: uuid().primaryKey().defaultRandom(),
|
|
206
|
+
* owner: text().notNull(),
|
|
207
|
+
* title: text().notNull(),
|
|
208
|
+
* },
|
|
209
|
+
* rls: true,
|
|
210
|
+
* policies: (p) => [
|
|
211
|
+
* policy("owner_all").for("all").to("authenticated")
|
|
212
|
+
* .using(p.col("owner").eq(p.auth.uid()))
|
|
213
|
+
* .withCheck(p.col("owner").eq(p.auth.uid())),
|
|
214
|
+
* ],
|
|
215
|
+
* });
|
|
216
|
+
*
|
|
217
|
+
* export default defineSchema("public", { tables: [todos] });
|
|
218
|
+
*
|
|
219
|
+
* The dictionary form `defineSchema({ tables: { todos: {…} } })` is RETIRED and
|
|
220
|
+
* refused by name: a table whose name comes from a key does not know what it is
|
|
221
|
+
* called while it is being built, so it cannot resolve `references(() =>
|
|
222
|
+
* other.id)`, and a schema that does not say its own name cannot tell one
|
|
223
|
+
* `invoices` from another schema's `invoices`.
|
|
224
|
+
*
|
|
225
|
+
* The returned value is `{ name, tables: { todos: { name, columns, rls,
|
|
226
|
+
* policies } }, extensions, exposed }` — the exact shape the runtime schema
|
|
227
|
+
* extractor parses. Per-column phantom types are preserved so
|
|
228
|
+
* `Database.public.todos.insert({...})` stays typed.
|
|
229
|
+
*
|
|
230
|
+
* RLS normalization: `rls` defaults to **`true`**, `policies` to `[]`. A table
|
|
231
|
+
* that declares neither is therefore deny-all — nothing reads it until a policy
|
|
232
|
+
* says who may, which is the safe starting point rather than a bug. Declare
|
|
233
|
+
* `rls: false` for a genuinely public table; that is an explicit, greppable
|
|
234
|
+
* statement of intent instead of an omission. When `policies` is non-empty,
|
|
235
|
+
* `rls` is forced on (ENABLE + FORCE) regardless of the declared flag — a table
|
|
236
|
+
* with policies must have RLS enabled or the policies would be inert.
|
|
237
|
+
*/
|
|
238
|
+
/**
|
|
239
|
+
* Where a table's metadata lives.
|
|
240
|
+
*
|
|
241
|
+
* The table VALUE is its columns, so `lists.id` is the column — which means a
|
|
242
|
+
* column called `name` would overwrite the table's own name if metadata sat in
|
|
243
|
+
* a plain field. Measured, not imagined: the existing `users` / `rooms` tests
|
|
244
|
+
* (both declare a `name` column) produced an `[object Object]` table key.
|
|
245
|
+
* `columns`, `rls`, `unique` and `indexes` are all plausible column
|
|
246
|
+
* names too. A column cannot collide with a symbol.
|
|
247
|
+
*/
|
|
248
|
+
export declare const TABLE_META: unique symbol;
|
|
249
|
+
/**
|
|
250
|
+
* A value produced by `defineTable`: the columns, plus metadata behind the
|
|
251
|
+
* symbol. `S` is the table's search as declared: with one, the table's type
|
|
252
|
+
* says so, and `search()` is typed for it.
|
|
253
|
+
*/
|
|
254
|
+
export type TableHandle<C extends ColumnMap = ColumnMap, N extends string = string, S = undefined> = C & {
|
|
255
|
+
readonly [TABLE_META]: TableDef<C> & {
|
|
256
|
+
name: N;
|
|
257
|
+
} & ([S] extends [undefined] ? unknown : {
|
|
258
|
+
search: Search;
|
|
259
|
+
});
|
|
260
|
+
};
|
|
261
|
+
/**
|
|
262
|
+
* Declare one table as a standalone value that KNOWS ITS OWN NAME.
|
|
263
|
+
*
|
|
264
|
+
* The name lives here, not in a dictionary key. A table whose name comes from a
|
|
265
|
+
* key cannot resolve `references(() => other.id)` when it is built — it does not
|
|
266
|
+
* know what to call itself — which forces a two-phase resolution with an
|
|
267
|
+
* empty-name intermediate state. One name source, no empty identity.
|
|
268
|
+
*
|
|
269
|
+
* The returned object spreads its own columns, so a sibling can point at it:
|
|
270
|
+
* `references(() => lists.id)`.
|
|
271
|
+
*/
|
|
272
|
+
export declare function defineTable<const N extends string, C extends ColumnMap, const S extends SearchInput<Extract<keyof C, string>> | undefined = undefined>(name: N, input: TableInput<C> & {
|
|
273
|
+
search?: S;
|
|
274
|
+
}): TableHandle<C, N, S>;
|
|
275
|
+
/**
|
|
276
|
+
* Collect declared tables into one schema.
|
|
277
|
+
*
|
|
278
|
+
* Takes an ARRAY, not a dictionary: the tables already carry their names, and a
|
|
279
|
+
* second name source is a second thing that can disagree.
|
|
280
|
+
*/
|
|
281
|
+
export declare function defineSchema<const T extends readonly TableHandle[]>(name: string, input: {
|
|
282
|
+
tables: T;
|
|
283
|
+
exposed?: boolean;
|
|
284
|
+
extensions?: string[];
|
|
285
|
+
raw?: RawUnitDef[];
|
|
286
|
+
}): SchemaDef<TablesByName<T>>;
|
|
287
|
+
export { check, foreignKey, toForeignKeyDef };
|
|
288
|
+
export type { CheckDef, ForeignKeyBuilder, ForeignKeyDef };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type Policy, type Schema, type Table } from '../model.js';
|
|
2
|
+
import type { SchemaDef } from './schema.js';
|
|
3
|
+
/** One or more schemas (db/public.ts, db/billing.ts, …) as the model. */
|
|
4
|
+
export declare function toModel(schemas: SchemaDef | SchemaDef[]): Schema;
|
|
5
|
+
/** The columns of `t` a policy's SQL names quoted — as the renderer writes a column; a hand-written policy that quotes nothing names none. */
|
|
6
|
+
export declare function policyColumns(t: Table, p: Policy): string[];
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type Binding } from './connect.js';
|
|
2
|
+
import { type Schema } from './model.js';
|
|
3
|
+
interface Begin {
|
|
4
|
+
begin<T>(fn: (tx: {
|
|
5
|
+
unsafe(sql: string, params?: unknown[]): PromiseLike<unknown>;
|
|
6
|
+
}) => Promise<T>): Promise<T>;
|
|
7
|
+
}
|
|
8
|
+
export declare function forgetUser(sql: Begin, binding: Binding, schema: Schema, userId: string, serviceRole?: string): Promise<{
|
|
9
|
+
deleted: number;
|
|
10
|
+
detached: number;
|
|
11
|
+
}>;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export { canonicalType, digest, emptySchema, normalize, SchemaError, validate } from './model.js';
|
|
2
|
+
export type { Check, Column, Enum, FkAction, ForeignKey, Index, IndexKey, Policy, Raw, Schema, Search, SearchMetric, Table, Unique } from './model.js';
|
|
3
|
+
export { plan, versionSchema } from './plan.js';
|
|
4
|
+
export type { Layout, Plan, PlanInput, Roles, Step } from './plan.js';
|
|
5
|
+
export { applicationName, cutOff, DEFAULT_ROLES, isDirect, MigrationOpen, MigrationRefused, Migrator, RowsHeld, searchPath, VersionStillConnected } from './migrate.js';
|
|
6
|
+
export type { Adoption, MigratorOptions, Pool, Reserved, StartResult, Status, Version } from './migrate.js';
|
|
7
|
+
export { bind, binding, bindStatement, VersionNotMigrated } from './connect.js';
|
|
8
|
+
export type { Binding } from './connect.js';
|
|
9
|
+
export { forgetUser } from './forget.js';
|
|
10
|
+
export { adoptionBaseline, drift, type Baseline } from './adopt.js';
|
|
11
|
+
export { introspect, type Introspected } from './introspect.js';
|
|
12
|
+
export { pullSchema, type Pulled } from './pull.js';
|
|
13
|
+
export { SEARCH_SCHEMA, vectorTable, type SearchGeneration } from './search.js';
|
|
14
|
+
export { createSearchWorker, type SearchWorker, type SearchWorkerOptions } from './search-worker.js';
|
|
15
|
+
export * from './query/index.js';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type Schema } from './model.js';
|
|
2
|
+
import type { Pool } from './migrate.js';
|
|
3
|
+
import type { Roles } from './plan.js';
|
|
4
|
+
import { type SchemaUnit } from './units.js';
|
|
5
|
+
export interface Introspected {
|
|
6
|
+
schema: Schema;
|
|
7
|
+
/** What the database holds that the model cannot say, one line each: left as it is. */
|
|
8
|
+
unmanaged: string[];
|
|
9
|
+
/** Tables whose row-level security is FORCEd on their owner, as v2 made every table. */
|
|
10
|
+
forced: string[];
|
|
11
|
+
/** The objects outside the model by kind and name, each with Postgres' text of it: what a raw unit makes is compared by these. */
|
|
12
|
+
objects: Map<string, string>;
|
|
13
|
+
/** Those of them a schema file can carry as raw units, in an order they can be made in (units.ts). */
|
|
14
|
+
units: SchemaUnit[];
|
|
15
|
+
}
|
|
16
|
+
/** Reads `schemas` (and the enums and extensions they use) off the catalog, on one connection of `pool`. */
|
|
17
|
+
export declare function introspect(pool: Pool, schemas: readonly string[], roles: Roles): Promise<Introspected>;
|