palgres 0.0.0-stage → 0.1.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.
Files changed (50) hide show
  1. package/LICENSE +27 -0
  2. package/README.md +2 -2
  3. package/dist/chunks/5DSZT7RI.js +5 -0
  4. package/dist/chunks/7676QADW.js +2 -0
  5. package/dist/chunks/W7VRQDZA.js +2 -0
  6. package/dist/dsl.js +2 -0
  7. package/dist/index.js +372 -0
  8. package/dist/testing.js +2 -0
  9. package/package.json +32 -4
  10. package/types/internal/src/data.d.ts +15 -0
  11. package/types/palgres/src/adopt.d.ts +17 -0
  12. package/types/palgres/src/connect.d.ts +37 -0
  13. package/types/palgres/src/dsl/columns.d.ts +388 -0
  14. package/types/palgres/src/dsl/foreign-key.d.ts +33 -0
  15. package/types/palgres/src/dsl/index.d.ts +11 -0
  16. package/types/palgres/src/dsl/policy.d.ts +181 -0
  17. package/types/palgres/src/dsl/raw.d.ts +43 -0
  18. package/types/palgres/src/dsl/schema.d.ts +288 -0
  19. package/types/palgres/src/dsl/to-model.d.ts +6 -0
  20. package/types/palgres/src/forget.d.ts +12 -0
  21. package/types/palgres/src/index.d.ts +15 -0
  22. package/types/palgres/src/introspect.d.ts +17 -0
  23. package/types/palgres/src/migrate.d.ts +189 -0
  24. package/types/palgres/src/model.d.ts +181 -0
  25. package/types/palgres/src/plan.d.ts +107 -0
  26. package/types/palgres/src/pull.d.ts +13 -0
  27. package/types/palgres/src/query/admission.d.ts +80 -0
  28. package/types/palgres/src/query/catalog.d.ts +50 -0
  29. package/types/palgres/src/query/client.d.ts +154 -0
  30. package/types/palgres/src/query/data-mark.d.ts +7 -0
  31. package/types/palgres/src/query/diagnostics.d.ts +76 -0
  32. package/types/palgres/src/query/driver.d.ts +299 -0
  33. package/types/palgres/src/query/env-gen.d.ts +14 -0
  34. package/types/palgres/src/query/errors.d.ts +168 -0
  35. package/types/palgres/src/query/guards.d.ts +62 -0
  36. package/types/palgres/src/query/index.d.ts +76 -0
  37. package/types/palgres/src/query/options.d.ts +75 -0
  38. package/types/palgres/src/query/relations.d.ts +15 -0
  39. package/types/palgres/src/query/repository.d.ts +42 -0
  40. package/types/palgres/src/query/request.d.ts +54 -0
  41. package/types/palgres/src/query/search.d.ts +55 -0
  42. package/types/palgres/src/query/surface.d.ts +9 -0
  43. package/types/palgres/src/query/tx-plan.d.ts +229 -0
  44. package/types/palgres/src/query/typed.d.ts +537 -0
  45. package/types/palgres/src/query/unique.d.ts +16 -0
  46. package/types/palgres/src/search-worker.d.ts +36 -0
  47. package/types/palgres/src/search.d.ts +107 -0
  48. package/types/palgres/src/testing/fake.d.ts +22 -0
  49. package/types/palgres/src/testing/index.d.ts +2 -0
  50. 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>;