@palbase/backend 3.0.0 → 4.0.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 (37) hide show
  1. package/dist/{chunk-B7EUJP5W.js → chunk-EG7TTYHY.js} +113 -3
  2. package/dist/chunk-EG7TTYHY.js.map +1 -0
  3. package/dist/{chunk-PHAFZGHN.js → chunk-WUQO76NW.js} +26 -19
  4. package/dist/chunk-WUQO76NW.js.map +1 -0
  5. package/dist/db/index.cjs +117 -2
  6. package/dist/db/index.cjs.map +1 -1
  7. package/dist/db/index.d.cts +2 -2
  8. package/dist/db/index.d.ts +2 -2
  9. package/dist/db/index.js +11 -1
  10. package/dist/{endpoint-DJ98tQd6.d.cts → endpoint-2d_DpASt.d.cts} +92 -56
  11. package/dist/{endpoint-DJ98tQd6.d.ts → endpoint-2d_DpASt.d.ts} +92 -56
  12. package/dist/{index-CXUs9iTQ.d.ts → index-DZW9CjiY.d.ts} +210 -41
  13. package/dist/{index-CZAwpQE1.d.cts → index-DzRFS3Tl.d.cts} +210 -41
  14. package/dist/index.cjs +371 -42
  15. package/dist/index.cjs.map +1 -1
  16. package/dist/index.d.cts +88 -215
  17. package/dist/index.d.ts +88 -215
  18. package/dist/index.js +217 -21
  19. package/dist/index.js.map +1 -1
  20. package/dist/test/index.cjs +34 -19
  21. package/dist/test/index.cjs.map +1 -1
  22. package/dist/test/index.d.cts +1 -1
  23. package/dist/test/index.d.ts +1 -1
  24. package/dist/test/index.js +10 -2
  25. package/dist/test/index.js.map +1 -1
  26. package/docs/README.md +11 -11
  27. package/docs/database.md +40 -0
  28. package/docs/endpoints.md +98 -92
  29. package/docs/errors.md +37 -30
  30. package/docs/getting-started.md +24 -20
  31. package/docs/llms-full.txt +401 -235
  32. package/docs/routing.md +39 -45
  33. package/docs/schema.md +134 -23
  34. package/docs/services.md +14 -10
  35. package/package.json +2 -2
  36. package/dist/chunk-B7EUJP5W.js.map +0 -1
  37. package/dist/chunk-PHAFZGHN.js.map +0 -1
@@ -1,5 +1,5 @@
1
1
  import { Tables, TableTypes } from './db/env.js';
2
- import { D as DBClient } from './endpoint-DJ98tQd6.js';
2
+ import { D as DBClient } from './endpoint-2d_DpASt.js';
3
3
 
4
4
  /** On delete action for foreign key references. */
5
5
  type OnDeleteAction = 'cascade' | 'set null' | 'restrict' | 'no action';
@@ -98,29 +98,165 @@ declare function jsonb(): ColumnBuilder<'jsonb', false, false, never>;
98
98
  */
99
99
  declare function enumType<const V extends readonly string[]>(name: string, values: V): ColumnBuilder<'enum', false, false, V[number]>;
100
100
 
101
+ /**
102
+ * policy.ts — the RLS policy authoring DSL.
103
+ *
104
+ * `policy(name)` returns a fluent builder that mirrors the `ColumnBuilder`
105
+ * style in columns.ts: each chainable method mutates the underlying
106
+ * definition and returns the builder so calls compose. The terminal value is
107
+ * a plain {@link PolicyDef} — the exact JSON shape the runtime's
108
+ * `schema_extract.js` reads off the bundled module and the Go side parses into
109
+ * `PolicyJSON` (CONTRACT-POLICY).
110
+ *
111
+ * @example
112
+ * import { policy } from "@palbase/backend";
113
+ *
114
+ * policy("owner_select")
115
+ * .for("select")
116
+ * .to("authenticated")
117
+ * .using("owner = (select auth.uid())");
118
+ */
119
+ /** The SQL command a policy applies to. `"all"` covers SELECT/INSERT/UPDATE/DELETE. */
120
+ type PolicyCommand = "all" | "select" | "insert" | "update" | "delete";
121
+ /** Whether a policy is permissive (OR-combined, the default) or restrictive
122
+ * (AND-combined). Mirrors Postgres `CREATE POLICY ... AS PERMISSIVE|RESTRICTIVE`. */
123
+ type PolicyMode = "permissive" | "restrictive";
124
+ /**
125
+ * The compiled, serializable policy definition — the EXACT shape consumed by
126
+ * `schema_extract.js` → Go `PolicyJSON` (CONTRACT-POLICY).
127
+ *
128
+ * - `roles`: the DB roles this policy applies to (`TO` clause). An empty array
129
+ * means the policy applies to PUBLIC (all roles) — the Postgres default.
130
+ * - `using`: the `USING (...)` row-visibility expression, or `null` when none.
131
+ * - `withCheck`: the `WITH CHECK (...)` write-validation expression, or `null`.
132
+ * - `permissive`: `true` for `AS PERMISSIVE` (default), `false` for restrictive.
133
+ */
134
+ interface PolicyDef {
135
+ name: string;
136
+ command: PolicyCommand;
137
+ roles: string[];
138
+ using: string | null;
139
+ withCheck: string | null;
140
+ permissive: boolean;
141
+ }
142
+ /**
143
+ * Fluent RLS policy builder.
144
+ *
145
+ * Defaults (documented, applied at construction):
146
+ * - `command`: `"all"` — applies to every SQL command unless `.for(...)` narrows it.
147
+ * - `roles`: `["authenticated"]` — the common case is "rule applies to signed-in
148
+ * users". Call `.to(...)` to override; pass `.to()` with no roles (or never
149
+ * call it after a reset) to target PUBLIC.
150
+ * - `using` / `withCheck`: `null` — no row filter / write check until set.
151
+ * - `permissive`: `true` — `AS PERMISSIVE` (policies OR together).
152
+ *
153
+ * Each method mutates `_def` in place and returns `this`, so the chain is a
154
+ * single builder instance (no per-call allocation, like a tagged-template
155
+ * compile target). The terminal `PolicyDef` is read directly off `_def` by
156
+ * `schema_extract.js`.
157
+ */
158
+ declare class PolicyBuilder {
159
+ readonly _def: PolicyDef;
160
+ constructor(name: string);
161
+ /** Restrict the policy to a single SQL command (default `"all"`). */
162
+ for(command: PolicyCommand): this;
163
+ /**
164
+ * Set the DB roles the policy applies to (the `TO` clause), replacing any
165
+ * previously-set roles. Call with no arguments to target PUBLIC (all roles).
166
+ *
167
+ * @example
168
+ * policy("p").to("authenticated")
169
+ * policy("p").to("authenticated", "service_role")
170
+ * policy("p").to() // PUBLIC
171
+ */
172
+ to(...roles: string[]): this;
173
+ /** Set the `USING (...)` row-visibility expression (raw SQL). */
174
+ using(sqlExpr: string): this;
175
+ /** Set the `WITH CHECK (...)` write-validation expression (raw SQL). */
176
+ withCheck(sqlExpr: string): this;
177
+ /** Set the policy mode: `"permissive"` (default, OR-combined) or
178
+ * `"restrictive"` (AND-combined). */
179
+ as(mode: PolicyMode): this;
180
+ }
181
+ /**
182
+ * Start authoring an RLS policy. Returns a {@link PolicyBuilder}; the resulting
183
+ * `PolicyBuilder` is accepted directly in a table's `policies: [...]` array
184
+ * (its `_def` is read at schema-extract time).
185
+ *
186
+ * @param name The policy name. Palbase reconciliation keys policies by
187
+ * `(table, name)`, so names must be unique per table.
188
+ */
189
+ declare function policy(name: string): PolicyBuilder;
190
+
191
+ /**
192
+ * Postgres extensions a Palbase project can enable from its schema.
193
+ *
194
+ * Extensions are config-as-code: declare them in `defineSchema({ extensions })`
195
+ * and the deploy installs them (CREATE EXTENSION … SCHEMA extensions) using the
196
+ * deploy path's privileged connection. They are NOT toggled live from Studio —
197
+ * CREATE EXTENSION requires a superuser role that only the deploy path holds.
198
+ *
199
+ * The list is an allowlist (a string-literal union) so editors autocomplete the
200
+ * supported names and a typo fails typecheck. It is intentionally extensible:
201
+ * add a name here (+ confirm the base image ships it) to support more.
202
+ */
203
+ declare const PALBASE_EXTENSIONS: readonly ["vector", "pg_trgm", "unaccent", "citext", "postgis", "cube", "earthdistance", "hstore", "ltree", "pg_cron", "pgcrypto", "uuid-ossp"];
204
+ /** A Postgres extension supported by Palbase (allowlist union). */
205
+ type PalbaseExtension = (typeof PALBASE_EXTENSIONS)[number];
206
+ /**
207
+ * Extensions that depend on another extension. The deploy installs
208
+ * dependencies first; declaring `earthdistance` without `cube` still works
209
+ * because the deploy resolves the order, but listing both is clearer.
210
+ */
211
+ declare const EXTENSION_DEPENDENCIES: Partial<Record<PalbaseExtension, PalbaseExtension[]>>;
212
+ /** Runtime guard: is `name` a supported Palbase extension? */
213
+ declare function isPalbaseExtension(name: string): name is PalbaseExtension;
214
+
101
215
  /**
102
216
  * A map of column builders keyed by column name — the value you write under
103
- * each key of `defineSchema({ tables: { <name>: <columns> } })`.
217
+ * the `columns` key of `defineSchema({ tables: { <name>: { columns } } })`.
104
218
  *
105
219
  * The default `Record<string, ColumnBuilder>` keeps bare references compiling
106
220
  * without a type argument.
107
221
  */
108
222
  type ColumnMap = Record<string, ColumnBuilder>;
109
223
  /**
110
- * A table definition with its name and columns.
224
+ * The author-facing value written under each table key:
225
+ * `{ columns, rls?, policies? }`.
226
+ *
227
+ * - `columns`: the column map (required).
228
+ * - `rls`: enable + FORCE row-level security on this table. Implied when
229
+ * `policies` is non-empty; set it explicitly to enable RLS with no policies
230
+ * yet (deny-all — useful only as an intermediate step).
231
+ * - `policies`: the RLS policies for this table, authored with `policy(name)`.
232
+ * Each entry may be a {@link PolicyBuilder} (the normal `policy(...)` chain)
233
+ * or a raw {@link PolicyDef} object.
234
+ *
235
+ * The `C` type parameter preserves the precise per-column phantom types so the
236
+ * typed `Database.tables.*` surface keeps inferring insert/row shapes.
237
+ */
238
+ interface TableInput<C extends ColumnMap = ColumnMap> {
239
+ columns: C;
240
+ rls?: boolean;
241
+ policies?: (PolicyBuilder | PolicyDef)[];
242
+ }
243
+ /**
244
+ * A table definition — the runtime value the Go runtime's `schema_extract.js`
245
+ * reads. It keys tables by `tableDef.name`, reads `tableDef.columns` for the
246
+ * column DDL, and `tableDef.rls` + `tableDef.policies` for RLS.
111
247
  *
112
- * The runtime value shape — `{ name, columns }` — is the contract the Go
113
- * runtime's `schema_extract.js` reads (it keys tables by `tableDef.name`).
114
248
  * `defineSchema` derives `name` from the object key, so authors never repeat
115
- * the table name.
249
+ * the table name. `rls`/`policies` are always present after normalization
250
+ * (defaulted to `false`/`[]`).
116
251
  *
117
- * The `C` type parameter preserves the precise per-column phantom types so
118
- * that downstream mapped types (InsertShape, RowShape) can discriminate on
119
- * them.
252
+ * The `C` type parameter preserves the precise per-column phantom types so that
253
+ * downstream mapped types (InsertShape, RowShape) can discriminate on them.
120
254
  */
121
255
  interface TableDef<C extends ColumnMap = ColumnMap> {
122
256
  name: string;
123
257
  columns: C;
258
+ rls: boolean;
259
+ policies: PolicyDef[];
124
260
  }
125
261
  /**
126
262
  * A schema definition containing multiple tables, keyed by table name.
@@ -131,49 +267,61 @@ interface TableDef<C extends ColumnMap = ColumnMap> {
131
267
  */
132
268
  interface SchemaDef<T extends Record<string, TableDef> = Record<string, TableDef>> {
133
269
  tables: T;
270
+ /** Postgres extensions to install on deploy. Normalized to `[]` when absent. */
271
+ extensions: PalbaseExtension[];
134
272
  }
135
273
  /** The author-facing input to `defineSchema` — a `tables` map whose keys are
136
- * the table names and whose values are the column maps. */
137
- interface SchemaInput<T extends Record<string, ColumnMap> = Record<string, ColumnMap>> {
274
+ * the table names and whose values are `{ columns, rls?, policies? }`, plus an
275
+ * optional `extensions` allowlist. */
276
+ interface SchemaInput<T extends Record<string, TableInput> = Record<string, TableInput>> {
138
277
  tables: T;
278
+ /**
279
+ * Postgres extensions to enable for this project, e.g. `["vector"]`.
280
+ * Config-as-code: installed by the deploy (CREATE EXTENSION … SCHEMA
281
+ * extensions) with the privileged deploy connection. The type is an
282
+ * allowlist union, so unsupported names fail typecheck.
283
+ */
284
+ extensions?: PalbaseExtension[];
139
285
  }
140
- /** Map the author's `{ tables: { <name>: <columns> } }` input to the
141
- * `{ tables: { <name>: TableDef<columns> } }` runtime/type shape. */
142
- type TablesFromInput<T extends Record<string, ColumnMap>> = {
143
- [K in keyof T]: TableDef<T[K]>;
286
+ /** Map the author's `{ tables: { <name>: { columns } } }` input to the
287
+ * `{ tables: { <name>: TableDef<columns> } }` runtime/type shape, threading the
288
+ * per-table column map `T[K]["columns"]` so column-level inference survives. */
289
+ type TablesFromInput<T extends Record<string, TableInput>> = {
290
+ [K in keyof T]: TableDef<T[K]["columns"]>;
144
291
  };
145
292
  /**
146
- * Define a schema. The table NAME comes from the object key there is one
147
- * canonical form:
293
+ * Define a schema. The table NAME comes from the object key. Each table value
294
+ * is `{ columns, rls?, policies? }`:
148
295
  *
149
296
  * export default defineSchema({
150
297
  * tables: {
151
298
  * todos: {
152
- * id: uuid().primaryKey().defaultRandom(),
153
- * title: text().notNull(),
299
+ * columns: {
300
+ * id: uuid().primaryKey().defaultRandom(),
301
+ * owner: text().notNull(),
302
+ * title: text().notNull(),
303
+ * },
304
+ * rls: true,
305
+ * policies: [
306
+ * policy("owner_all").for("all").to("authenticated")
307
+ * .using("owner = (select auth.uid())")
308
+ * .withCheck("owner = (select auth.uid())"),
309
+ * ],
154
310
  * },
155
311
  * },
156
312
  * });
157
313
  *
158
- * The returned value is `{ tables: { todos: { name: "todos", columns: {...} } } }`
159
- * the exact shape the runtime schema extractor parses. Per-column phantom
160
- * types are preserved so `Database.tables.todos.insert({...})` is typed.
314
+ * The returned value is
315
+ * `{ tables: { todos: { name, columns, rls, policies } } }` — the exact shape
316
+ * the runtime schema extractor parses. Per-column phantom types are preserved
317
+ * so `Database.tables.todos.insert({...})` stays typed.
161
318
  *
162
- * @example
163
- * import { defineSchema, uuid, text, timestamp } from "@palbase/backend";
164
- *
165
- * export default defineSchema({
166
- * tables: {
167
- * todos: {
168
- * id: uuid().primaryKey().defaultRandom(),
169
- * title: text().notNull(),
170
- * done: boolean().default(false),
171
- * created_at: timestamp().defaultNow(),
172
- * },
173
- * },
174
- * });
319
+ * RLS normalization: `rls` defaults to `false`, `policies` to `[]`. When
320
+ * `policies` is non-empty, `rls` is forced on (ENABLE + FORCE) regardless of
321
+ * the declared `rls` flag — a table with policies must have RLS enabled or the
322
+ * policies would be inert.
175
323
  */
176
- declare function defineSchema<T extends Record<string, ColumnMap>>(input: SchemaInput<T>): SchemaDef<TablesFromInput<T>>;
324
+ declare function defineSchema<T extends Record<string, TableInput>>(input: SchemaInput<T>): SchemaDef<TablesFromInput<T>>;
177
325
 
178
326
  /**
179
327
  * typed-db.ts — Task 2: TypedDB schema-derived insert/row shapes.
@@ -278,17 +426,38 @@ type EnvTables = {
278
426
  interface EnvTypedTx {
279
427
  tables: EnvTables;
280
428
  }
429
+ /**
430
+ * The RLS-bypass sibling returned by `Database.asService()`. Same typed surface
431
+ * as {@link EnvTypedDatabase} — `tables`, the raw string ops, and a typed
432
+ * `transaction` — but it does NOT re-expose `asService` (no double-bypass).
433
+ * Every op it performs runs as the `service_role` (BYPASSRLS).
434
+ */
435
+ interface EnvServiceDatabase extends Omit<DBClient, "transaction" | "asService"> {
436
+ tables: EnvTables;
437
+ transaction<T>(fn: (tx: EnvTypedTx) => Promise<T>): Promise<T>;
438
+ }
281
439
  /**
282
440
  * The typed-by-default Database surface: the raw string-keyed `DBClient` ops
283
- * PLUS a `tables` map typed against the project's generated `palbase-env.d.ts`
284
- * and a `transaction` whose callback receives the typed tables.
441
+ * PLUS a `tables` map typed against the project's generated `palbase-env.d.ts`,
442
+ * a `transaction` whose callback receives the typed tables, and `asService()`
443
+ * for the explicit RLS-bypass sibling.
285
444
  *
286
445
  * `transaction` is declared here (overriding `DBClient["transaction"]`) so the
287
- * `tx` the callback receives carries the typed `.tables` API.
446
+ * `tx` the callback receives carries the typed `.tables` API. `asService` is
447
+ * re-typed to return the typed {@link EnvServiceDatabase} sibling.
288
448
  */
289
- interface EnvTypedDatabase extends Omit<DBClient, "transaction"> {
449
+ interface EnvTypedDatabase extends Omit<DBClient, "transaction" | "asService"> {
290
450
  tables: EnvTables;
291
451
  transaction<T>(fn: (tx: EnvTypedTx) => Promise<T>): Promise<T>;
452
+ /**
453
+ * Return a sibling that bypasses RLS by running as the `service_role`. Use
454
+ * sparingly and explicitly — the default `Database.*` path is RLS-enforced.
455
+ *
456
+ * @example
457
+ * const all = await Database.asService().tables.todos.findMany({});
458
+ * const rows = await Database.asService().query("SELECT * FROM todos");
459
+ */
460
+ asService(): EnvServiceDatabase;
292
461
  }
293
462
 
294
- export { ColumnBuilder as C, type EnvTypedDatabase as E, type InsertShape as I, type OnDeleteAction as O, type RowShape as R, type SchemaDef as S, type TableDef as T, type ColumnDef as a, type ColumnMap as b, type ColumnType as c, type EnvTables as d, type EnvTypedTable as e, type EnvTypedTx as f, type SchemaInput as g, type TypedDB as h, type TypedTable as i, type TypedTx as j, boolean as k, defineSchema as l, enumType as m, integer as n, jsonb as o, makeTypedDB as p, timestamp as q, text as t, uuid as u };
463
+ export { text as A, timestamp as B, ColumnBuilder as C, uuid as D, type EnvTypedDatabase as E, type InsertShape as I, type OnDeleteAction as O, PALBASE_EXTENSIONS as P, type RowShape as R, type SchemaDef as S, type TableDef as T, type ColumnDef as a, type ColumnMap as b, type ColumnType as c, EXTENSION_DEPENDENCIES as d, type EnvServiceDatabase as e, type EnvTables as f, type EnvTypedTable as g, type EnvTypedTx as h, type PalbaseExtension as i, PolicyBuilder as j, type PolicyCommand as k, type PolicyDef as l, type PolicyMode as m, type SchemaInput as n, type TableInput as o, type TypedDB as p, type TypedTable as q, type TypedTx as r, boolean as s, defineSchema as t, enumType as u, integer as v, isPalbaseExtension as w, jsonb as x, makeTypedDB as y, policy as z };
@@ -1,5 +1,5 @@
1
1
  import { Tables, TableTypes } from './db/env.cjs';
2
- import { D as DBClient } from './endpoint-DJ98tQd6.cjs';
2
+ import { D as DBClient } from './endpoint-2d_DpASt.cjs';
3
3
 
4
4
  /** On delete action for foreign key references. */
5
5
  type OnDeleteAction = 'cascade' | 'set null' | 'restrict' | 'no action';
@@ -98,29 +98,165 @@ declare function jsonb(): ColumnBuilder<'jsonb', false, false, never>;
98
98
  */
99
99
  declare function enumType<const V extends readonly string[]>(name: string, values: V): ColumnBuilder<'enum', false, false, V[number]>;
100
100
 
101
+ /**
102
+ * policy.ts — the RLS policy authoring DSL.
103
+ *
104
+ * `policy(name)` returns a fluent builder that mirrors the `ColumnBuilder`
105
+ * style in columns.ts: each chainable method mutates the underlying
106
+ * definition and returns the builder so calls compose. The terminal value is
107
+ * a plain {@link PolicyDef} — the exact JSON shape the runtime's
108
+ * `schema_extract.js` reads off the bundled module and the Go side parses into
109
+ * `PolicyJSON` (CONTRACT-POLICY).
110
+ *
111
+ * @example
112
+ * import { policy } from "@palbase/backend";
113
+ *
114
+ * policy("owner_select")
115
+ * .for("select")
116
+ * .to("authenticated")
117
+ * .using("owner = (select auth.uid())");
118
+ */
119
+ /** The SQL command a policy applies to. `"all"` covers SELECT/INSERT/UPDATE/DELETE. */
120
+ type PolicyCommand = "all" | "select" | "insert" | "update" | "delete";
121
+ /** Whether a policy is permissive (OR-combined, the default) or restrictive
122
+ * (AND-combined). Mirrors Postgres `CREATE POLICY ... AS PERMISSIVE|RESTRICTIVE`. */
123
+ type PolicyMode = "permissive" | "restrictive";
124
+ /**
125
+ * The compiled, serializable policy definition — the EXACT shape consumed by
126
+ * `schema_extract.js` → Go `PolicyJSON` (CONTRACT-POLICY).
127
+ *
128
+ * - `roles`: the DB roles this policy applies to (`TO` clause). An empty array
129
+ * means the policy applies to PUBLIC (all roles) — the Postgres default.
130
+ * - `using`: the `USING (...)` row-visibility expression, or `null` when none.
131
+ * - `withCheck`: the `WITH CHECK (...)` write-validation expression, or `null`.
132
+ * - `permissive`: `true` for `AS PERMISSIVE` (default), `false` for restrictive.
133
+ */
134
+ interface PolicyDef {
135
+ name: string;
136
+ command: PolicyCommand;
137
+ roles: string[];
138
+ using: string | null;
139
+ withCheck: string | null;
140
+ permissive: boolean;
141
+ }
142
+ /**
143
+ * Fluent RLS policy builder.
144
+ *
145
+ * Defaults (documented, applied at construction):
146
+ * - `command`: `"all"` — applies to every SQL command unless `.for(...)` narrows it.
147
+ * - `roles`: `["authenticated"]` — the common case is "rule applies to signed-in
148
+ * users". Call `.to(...)` to override; pass `.to()` with no roles (or never
149
+ * call it after a reset) to target PUBLIC.
150
+ * - `using` / `withCheck`: `null` — no row filter / write check until set.
151
+ * - `permissive`: `true` — `AS PERMISSIVE` (policies OR together).
152
+ *
153
+ * Each method mutates `_def` in place and returns `this`, so the chain is a
154
+ * single builder instance (no per-call allocation, like a tagged-template
155
+ * compile target). The terminal `PolicyDef` is read directly off `_def` by
156
+ * `schema_extract.js`.
157
+ */
158
+ declare class PolicyBuilder {
159
+ readonly _def: PolicyDef;
160
+ constructor(name: string);
161
+ /** Restrict the policy to a single SQL command (default `"all"`). */
162
+ for(command: PolicyCommand): this;
163
+ /**
164
+ * Set the DB roles the policy applies to (the `TO` clause), replacing any
165
+ * previously-set roles. Call with no arguments to target PUBLIC (all roles).
166
+ *
167
+ * @example
168
+ * policy("p").to("authenticated")
169
+ * policy("p").to("authenticated", "service_role")
170
+ * policy("p").to() // PUBLIC
171
+ */
172
+ to(...roles: string[]): this;
173
+ /** Set the `USING (...)` row-visibility expression (raw SQL). */
174
+ using(sqlExpr: string): this;
175
+ /** Set the `WITH CHECK (...)` write-validation expression (raw SQL). */
176
+ withCheck(sqlExpr: string): this;
177
+ /** Set the policy mode: `"permissive"` (default, OR-combined) or
178
+ * `"restrictive"` (AND-combined). */
179
+ as(mode: PolicyMode): this;
180
+ }
181
+ /**
182
+ * Start authoring an RLS policy. Returns a {@link PolicyBuilder}; the resulting
183
+ * `PolicyBuilder` is accepted directly in a table's `policies: [...]` array
184
+ * (its `_def` is read at schema-extract time).
185
+ *
186
+ * @param name The policy name. Palbase reconciliation keys policies by
187
+ * `(table, name)`, so names must be unique per table.
188
+ */
189
+ declare function policy(name: string): PolicyBuilder;
190
+
191
+ /**
192
+ * Postgres extensions a Palbase project can enable from its schema.
193
+ *
194
+ * Extensions are config-as-code: declare them in `defineSchema({ extensions })`
195
+ * and the deploy installs them (CREATE EXTENSION … SCHEMA extensions) using the
196
+ * deploy path's privileged connection. They are NOT toggled live from Studio —
197
+ * CREATE EXTENSION requires a superuser role that only the deploy path holds.
198
+ *
199
+ * The list is an allowlist (a string-literal union) so editors autocomplete the
200
+ * supported names and a typo fails typecheck. It is intentionally extensible:
201
+ * add a name here (+ confirm the base image ships it) to support more.
202
+ */
203
+ declare const PALBASE_EXTENSIONS: readonly ["vector", "pg_trgm", "unaccent", "citext", "postgis", "cube", "earthdistance", "hstore", "ltree", "pg_cron", "pgcrypto", "uuid-ossp"];
204
+ /** A Postgres extension supported by Palbase (allowlist union). */
205
+ type PalbaseExtension = (typeof PALBASE_EXTENSIONS)[number];
206
+ /**
207
+ * Extensions that depend on another extension. The deploy installs
208
+ * dependencies first; declaring `earthdistance` without `cube` still works
209
+ * because the deploy resolves the order, but listing both is clearer.
210
+ */
211
+ declare const EXTENSION_DEPENDENCIES: Partial<Record<PalbaseExtension, PalbaseExtension[]>>;
212
+ /** Runtime guard: is `name` a supported Palbase extension? */
213
+ declare function isPalbaseExtension(name: string): name is PalbaseExtension;
214
+
101
215
  /**
102
216
  * A map of column builders keyed by column name — the value you write under
103
- * each key of `defineSchema({ tables: { <name>: <columns> } })`.
217
+ * the `columns` key of `defineSchema({ tables: { <name>: { columns } } })`.
104
218
  *
105
219
  * The default `Record<string, ColumnBuilder>` keeps bare references compiling
106
220
  * without a type argument.
107
221
  */
108
222
  type ColumnMap = Record<string, ColumnBuilder>;
109
223
  /**
110
- * A table definition with its name and columns.
224
+ * The author-facing value written under each table key:
225
+ * `{ columns, rls?, policies? }`.
226
+ *
227
+ * - `columns`: the column map (required).
228
+ * - `rls`: enable + FORCE row-level security on this table. Implied when
229
+ * `policies` is non-empty; set it explicitly to enable RLS with no policies
230
+ * yet (deny-all — useful only as an intermediate step).
231
+ * - `policies`: the RLS policies for this table, authored with `policy(name)`.
232
+ * Each entry may be a {@link PolicyBuilder} (the normal `policy(...)` chain)
233
+ * or a raw {@link PolicyDef} object.
234
+ *
235
+ * The `C` type parameter preserves the precise per-column phantom types so the
236
+ * typed `Database.tables.*` surface keeps inferring insert/row shapes.
237
+ */
238
+ interface TableInput<C extends ColumnMap = ColumnMap> {
239
+ columns: C;
240
+ rls?: boolean;
241
+ policies?: (PolicyBuilder | PolicyDef)[];
242
+ }
243
+ /**
244
+ * A table definition — the runtime value the Go runtime's `schema_extract.js`
245
+ * reads. It keys tables by `tableDef.name`, reads `tableDef.columns` for the
246
+ * column DDL, and `tableDef.rls` + `tableDef.policies` for RLS.
111
247
  *
112
- * The runtime value shape — `{ name, columns }` — is the contract the Go
113
- * runtime's `schema_extract.js` reads (it keys tables by `tableDef.name`).
114
248
  * `defineSchema` derives `name` from the object key, so authors never repeat
115
- * the table name.
249
+ * the table name. `rls`/`policies` are always present after normalization
250
+ * (defaulted to `false`/`[]`).
116
251
  *
117
- * The `C` type parameter preserves the precise per-column phantom types so
118
- * that downstream mapped types (InsertShape, RowShape) can discriminate on
119
- * them.
252
+ * The `C` type parameter preserves the precise per-column phantom types so that
253
+ * downstream mapped types (InsertShape, RowShape) can discriminate on them.
120
254
  */
121
255
  interface TableDef<C extends ColumnMap = ColumnMap> {
122
256
  name: string;
123
257
  columns: C;
258
+ rls: boolean;
259
+ policies: PolicyDef[];
124
260
  }
125
261
  /**
126
262
  * A schema definition containing multiple tables, keyed by table name.
@@ -131,49 +267,61 @@ interface TableDef<C extends ColumnMap = ColumnMap> {
131
267
  */
132
268
  interface SchemaDef<T extends Record<string, TableDef> = Record<string, TableDef>> {
133
269
  tables: T;
270
+ /** Postgres extensions to install on deploy. Normalized to `[]` when absent. */
271
+ extensions: PalbaseExtension[];
134
272
  }
135
273
  /** The author-facing input to `defineSchema` — a `tables` map whose keys are
136
- * the table names and whose values are the column maps. */
137
- interface SchemaInput<T extends Record<string, ColumnMap> = Record<string, ColumnMap>> {
274
+ * the table names and whose values are `{ columns, rls?, policies? }`, plus an
275
+ * optional `extensions` allowlist. */
276
+ interface SchemaInput<T extends Record<string, TableInput> = Record<string, TableInput>> {
138
277
  tables: T;
278
+ /**
279
+ * Postgres extensions to enable for this project, e.g. `["vector"]`.
280
+ * Config-as-code: installed by the deploy (CREATE EXTENSION … SCHEMA
281
+ * extensions) with the privileged deploy connection. The type is an
282
+ * allowlist union, so unsupported names fail typecheck.
283
+ */
284
+ extensions?: PalbaseExtension[];
139
285
  }
140
- /** Map the author's `{ tables: { <name>: <columns> } }` input to the
141
- * `{ tables: { <name>: TableDef<columns> } }` runtime/type shape. */
142
- type TablesFromInput<T extends Record<string, ColumnMap>> = {
143
- [K in keyof T]: TableDef<T[K]>;
286
+ /** Map the author's `{ tables: { <name>: { columns } } }` input to the
287
+ * `{ tables: { <name>: TableDef<columns> } }` runtime/type shape, threading the
288
+ * per-table column map `T[K]["columns"]` so column-level inference survives. */
289
+ type TablesFromInput<T extends Record<string, TableInput>> = {
290
+ [K in keyof T]: TableDef<T[K]["columns"]>;
144
291
  };
145
292
  /**
146
- * Define a schema. The table NAME comes from the object key there is one
147
- * canonical form:
293
+ * Define a schema. The table NAME comes from the object key. Each table value
294
+ * is `{ columns, rls?, policies? }`:
148
295
  *
149
296
  * export default defineSchema({
150
297
  * tables: {
151
298
  * todos: {
152
- * id: uuid().primaryKey().defaultRandom(),
153
- * title: text().notNull(),
299
+ * columns: {
300
+ * id: uuid().primaryKey().defaultRandom(),
301
+ * owner: text().notNull(),
302
+ * title: text().notNull(),
303
+ * },
304
+ * rls: true,
305
+ * policies: [
306
+ * policy("owner_all").for("all").to("authenticated")
307
+ * .using("owner = (select auth.uid())")
308
+ * .withCheck("owner = (select auth.uid())"),
309
+ * ],
154
310
  * },
155
311
  * },
156
312
  * });
157
313
  *
158
- * The returned value is `{ tables: { todos: { name: "todos", columns: {...} } } }`
159
- * the exact shape the runtime schema extractor parses. Per-column phantom
160
- * types are preserved so `Database.tables.todos.insert({...})` is typed.
314
+ * The returned value is
315
+ * `{ tables: { todos: { name, columns, rls, policies } } }` — the exact shape
316
+ * the runtime schema extractor parses. Per-column phantom types are preserved
317
+ * so `Database.tables.todos.insert({...})` stays typed.
161
318
  *
162
- * @example
163
- * import { defineSchema, uuid, text, timestamp } from "@palbase/backend";
164
- *
165
- * export default defineSchema({
166
- * tables: {
167
- * todos: {
168
- * id: uuid().primaryKey().defaultRandom(),
169
- * title: text().notNull(),
170
- * done: boolean().default(false),
171
- * created_at: timestamp().defaultNow(),
172
- * },
173
- * },
174
- * });
319
+ * RLS normalization: `rls` defaults to `false`, `policies` to `[]`. When
320
+ * `policies` is non-empty, `rls` is forced on (ENABLE + FORCE) regardless of
321
+ * the declared `rls` flag — a table with policies must have RLS enabled or the
322
+ * policies would be inert.
175
323
  */
176
- declare function defineSchema<T extends Record<string, ColumnMap>>(input: SchemaInput<T>): SchemaDef<TablesFromInput<T>>;
324
+ declare function defineSchema<T extends Record<string, TableInput>>(input: SchemaInput<T>): SchemaDef<TablesFromInput<T>>;
177
325
 
178
326
  /**
179
327
  * typed-db.ts — Task 2: TypedDB schema-derived insert/row shapes.
@@ -278,17 +426,38 @@ type EnvTables = {
278
426
  interface EnvTypedTx {
279
427
  tables: EnvTables;
280
428
  }
429
+ /**
430
+ * The RLS-bypass sibling returned by `Database.asService()`. Same typed surface
431
+ * as {@link EnvTypedDatabase} — `tables`, the raw string ops, and a typed
432
+ * `transaction` — but it does NOT re-expose `asService` (no double-bypass).
433
+ * Every op it performs runs as the `service_role` (BYPASSRLS).
434
+ */
435
+ interface EnvServiceDatabase extends Omit<DBClient, "transaction" | "asService"> {
436
+ tables: EnvTables;
437
+ transaction<T>(fn: (tx: EnvTypedTx) => Promise<T>): Promise<T>;
438
+ }
281
439
  /**
282
440
  * The typed-by-default Database surface: the raw string-keyed `DBClient` ops
283
- * PLUS a `tables` map typed against the project's generated `palbase-env.d.ts`
284
- * and a `transaction` whose callback receives the typed tables.
441
+ * PLUS a `tables` map typed against the project's generated `palbase-env.d.ts`,
442
+ * a `transaction` whose callback receives the typed tables, and `asService()`
443
+ * for the explicit RLS-bypass sibling.
285
444
  *
286
445
  * `transaction` is declared here (overriding `DBClient["transaction"]`) so the
287
- * `tx` the callback receives carries the typed `.tables` API.
446
+ * `tx` the callback receives carries the typed `.tables` API. `asService` is
447
+ * re-typed to return the typed {@link EnvServiceDatabase} sibling.
288
448
  */
289
- interface EnvTypedDatabase extends Omit<DBClient, "transaction"> {
449
+ interface EnvTypedDatabase extends Omit<DBClient, "transaction" | "asService"> {
290
450
  tables: EnvTables;
291
451
  transaction<T>(fn: (tx: EnvTypedTx) => Promise<T>): Promise<T>;
452
+ /**
453
+ * Return a sibling that bypasses RLS by running as the `service_role`. Use
454
+ * sparingly and explicitly — the default `Database.*` path is RLS-enforced.
455
+ *
456
+ * @example
457
+ * const all = await Database.asService().tables.todos.findMany({});
458
+ * const rows = await Database.asService().query("SELECT * FROM todos");
459
+ */
460
+ asService(): EnvServiceDatabase;
292
461
  }
293
462
 
294
- export { ColumnBuilder as C, type EnvTypedDatabase as E, type InsertShape as I, type OnDeleteAction as O, type RowShape as R, type SchemaDef as S, type TableDef as T, type ColumnDef as a, type ColumnMap as b, type ColumnType as c, type EnvTables as d, type EnvTypedTable as e, type EnvTypedTx as f, type SchemaInput as g, type TypedDB as h, type TypedTable as i, type TypedTx as j, boolean as k, defineSchema as l, enumType as m, integer as n, jsonb as o, makeTypedDB as p, timestamp as q, text as t, uuid as u };
463
+ export { text as A, timestamp as B, ColumnBuilder as C, uuid as D, type EnvTypedDatabase as E, type InsertShape as I, type OnDeleteAction as O, PALBASE_EXTENSIONS as P, type RowShape as R, type SchemaDef as S, type TableDef as T, type ColumnDef as a, type ColumnMap as b, type ColumnType as c, EXTENSION_DEPENDENCIES as d, type EnvServiceDatabase as e, type EnvTables as f, type EnvTypedTable as g, type EnvTypedTx as h, type PalbaseExtension as i, PolicyBuilder as j, type PolicyCommand as k, type PolicyDef as l, type PolicyMode as m, type SchemaInput as n, type TableInput as o, type TypedDB as p, type TypedTable as q, type TypedTx as r, boolean as s, defineSchema as t, enumType as u, integer as v, isPalbaseExtension as w, jsonb as x, makeTypedDB as y, policy as z };