@zeno-lib/db 0.3.1 → 0.4.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.
@@ -0,0 +1,103 @@
1
+ import { t as FunctionPolicyOperation } from "./function-names-hv2mj_x1.mjs";
2
+ import { SQL } from "drizzle-orm";
3
+ import { PgTable } from "drizzle-orm/pg-core";
4
+ //#region src/query.d.ts
5
+ /**
6
+ * PostgREST's `.single()`: exactly one row, or an error.
7
+ *
8
+ * `findFirst()` returns `undefined` for no rows and silently drops the rest
9
+ * when there are several, and `.returning()` hands back an array.
10
+ */
11
+ declare const one: <T>(rows: readonly T[]) => T;
12
+ /**
13
+ * PostgREST's `.maybeSingle()`: one row or none, erroring on more than one.
14
+ * Returns `undefined` rather than `null`, which is what `findFirst()` does.
15
+ */
16
+ declare const maybeOne: <T>(rows: readonly T[]) => T | undefined;
17
+ type ColumnKey<TTable extends PgTable> = Extract<keyof TTable["_"]["columns"], string>;
18
+ type ExcludedSetOptions<TTable extends PgTable> = {
19
+ /**
20
+ * The conflict target's column keys, never written back from `excluded`.
21
+ * The first one that exists is also the empty-set fallback. Default `"id"`.
22
+ */
23
+ target?: ColumnKey<TTable> | readonly ColumnKey<TTable>[];
24
+ };
25
+ /**
26
+ * The `set` for `onConflictDoUpdate` that reproduces PostgREST's
27
+ * `.upsert(row, { onConflict })`: on conflict, every column the caller
28
+ * supplied is overwritten from the proposed row (`col = excluded.col`).
29
+ *
30
+ * - A key carrying `undefined` is skipped. Drizzle omits an undefined column
31
+ * from the INSERT, so `excluded.x` would be the column default, and on a
32
+ * conflict `x = excluded.x` would overwrite a stored value with it.
33
+ * - The conflict target (`id` unless `target` says otherwise) is excluded.
34
+ * - An empty result falls back to `<target> = excluded.<target>`. Drizzle
35
+ * throws `No values to set` while *building* the statement, so a payload
36
+ * holding nothing but the key would fail even on the plain insert path.
37
+ * Writing the key back to itself is a no-op that keeps `.returning()`
38
+ * yielding the row, which `onConflictDoNothing` does not.
39
+ *
40
+ * With no target column in the table (a junction keyed only by its foreign
41
+ * keys, with the default target) the fallback cannot apply and the result
42
+ * stays empty: such a table wants `onConflictDoNothing` anyway.
43
+ *
44
+ * Column names come from the schema, never from `values`.
45
+ */
46
+ declare const excludedSet: <TTable extends PgTable>(table: TTable, values: Record<string, unknown>, { target }?: ExcludedSetOptions<TTable>) => Record<string, SQL>;
47
+ /**
48
+ * The same values with every `undefined` key dropped, so an insert and its
49
+ * `excludedSet` agree on which columns were supplied.
50
+ */
51
+ declare const definedValues: <T extends Record<string, unknown>>(values: T) => T;
52
+ /** Table name to the schema its `<prefix>_<operation>_<table>` functions live in. */
53
+ type FunctionPermissionTables = ReadonlyMap<string, string>;
54
+ type FunctionPermissionTablesOptions = {
55
+ /** Schemas whose tables are included. Default `["public"]`. */
56
+ schemas?: readonly string[];
57
+ };
58
+ /**
59
+ * The allowlist `selectFunctionPermissions` resolves a table name against,
60
+ * built from a Drizzle schema object (`import * as schema`) or the result of
61
+ * `defineRelations`. Build it once at module scope. Views and non-table
62
+ * exports are ignored; a table name that appears in two included schemas
63
+ * throws, since a bare name could not tell them apart.
64
+ */
65
+ declare const functionPermissionTables: (schemaObject: Record<string, unknown>, { schemas }?: FunctionPermissionTablesOptions) => FunctionPermissionTables;
66
+ type FunctionPermissions = Record<FunctionPolicyOperation, boolean>;
67
+ /**
68
+ * Anything with Drizzle's `execute` returning rows: a postgres-js client from
69
+ * `@zeno-lib/db`, a `PgAsyncDatabase`, or a transaction.
70
+ */
71
+ type QueryExecutor = {
72
+ execute: (query: SQL) => PromiseLike<readonly Record<string, unknown>[]>;
73
+ };
74
+ type SelectFunctionPermissionsOptions = {
75
+ /** From `functionPermissionTables`. */
76
+ tables: FunctionPermissionTables;
77
+ /** The table asked about, checked against `tables`. */
78
+ table: string;
79
+ /**
80
+ * The row, passed to every function but `insert` (there is no row yet) as
81
+ * a named argument. Omit to call all four with none.
82
+ */
83
+ id?: number | string;
84
+ /** Name of the functions' row parameter. Default `"id"`. */
85
+ argumentName?: string;
86
+ /** Function prefix, as given to `functionPolicies`. Default `"can"`. */
87
+ prefix?: string;
88
+ /** Schema the functions live in. Default: the table's own. */
89
+ functionSchema?: string;
90
+ };
91
+ /**
92
+ * The four `<prefix>_<operation>_<table>()` answers for one table, in one
93
+ * statement: the read side of `functionPolicies`, for showing or hiding UI.
94
+ *
95
+ * `table` is only ever a map key: every identifier that reaches the statement
96
+ * comes from `tables` and is quoted with `sql.identifier`. A function missing
97
+ * or with another signature raises `42883` (`SqlState.undefinedFunction`).
98
+ * A `null` answer, which a function gives for a row the caller cannot see,
99
+ * counts as `false`, as does anything else that is not `true`.
100
+ */
101
+ declare const selectFunctionPermissions: (db: QueryExecutor, { argumentName, functionSchema, id, prefix, table, tables }: SelectFunctionPermissionsOptions) => Promise<FunctionPermissions>;
102
+ //#endregion
103
+ export { ExcludedSetOptions, FunctionPermissionTables, FunctionPermissionTablesOptions, FunctionPermissions, QueryExecutor, SelectFunctionPermissionsOptions, definedValues, excludedSet, functionPermissionTables, maybeOne, one, selectFunctionPermissions };
package/dist/query.mjs ADDED
@@ -0,0 +1,115 @@
1
+ import { n as functionPolicyName, t as FUNCTION_POLICY_OPERATIONS } from "./function-names-BDc-AuFm.mjs";
2
+ import { getColumns, is, sql } from "drizzle-orm";
3
+ import { PgTable, getTableConfig } from "drizzle-orm/pg-core";
4
+ //#region src/query.ts
5
+ /**
6
+ * PostgREST's `.single()`: exactly one row, or an error.
7
+ *
8
+ * `findFirst()` returns `undefined` for no rows and silently drops the rest
9
+ * when there are several, and `.returning()` hands back an array.
10
+ */
11
+ const one = (rows) => {
12
+ if (rows.length !== 1) throw new Error(`Expected exactly one row, received ${rows.length}`);
13
+ return rows[0];
14
+ };
15
+ /**
16
+ * PostgREST's `.maybeSingle()`: one row or none, erroring on more than one.
17
+ * Returns `undefined` rather than `null`, which is what `findFirst()` does.
18
+ */
19
+ const maybeOne = (rows) => {
20
+ if (rows.length > 1) throw new Error(`Expected at most one row, received ${rows.length}`);
21
+ return rows[0];
22
+ };
23
+ /**
24
+ * The `set` for `onConflictDoUpdate` that reproduces PostgREST's
25
+ * `.upsert(row, { onConflict })`: on conflict, every column the caller
26
+ * supplied is overwritten from the proposed row (`col = excluded.col`).
27
+ *
28
+ * - A key carrying `undefined` is skipped. Drizzle omits an undefined column
29
+ * from the INSERT, so `excluded.x` would be the column default, and on a
30
+ * conflict `x = excluded.x` would overwrite a stored value with it.
31
+ * - The conflict target (`id` unless `target` says otherwise) is excluded.
32
+ * - An empty result falls back to `<target> = excluded.<target>`. Drizzle
33
+ * throws `No values to set` while *building* the statement, so a payload
34
+ * holding nothing but the key would fail even on the plain insert path.
35
+ * Writing the key back to itself is a no-op that keeps `.returning()`
36
+ * yielding the row, which `onConflictDoNothing` does not.
37
+ *
38
+ * With no target column in the table (a junction keyed only by its foreign
39
+ * keys, with the default target) the fallback cannot apply and the result
40
+ * stays empty: such a table wants `onConflictDoNothing` anyway.
41
+ *
42
+ * Column names come from the schema, never from `values`.
43
+ */
44
+ const excludedSet = (table, values, { target = "id" } = {}) => {
45
+ const columns = getColumns(table);
46
+ const targets = typeof target === "string" ? [target] : target;
47
+ const set = {};
48
+ for (const [key, column] of Object.entries(columns)) if (key in values && values[key] !== void 0 && !targets.includes(key)) set[key] = sql`excluded.${sql.identifier(column.name)}`;
49
+ if (Object.keys(set).length > 0) return set;
50
+ const fallback = targets.find((key) => key in columns);
51
+ const fallbackColumn = fallback === void 0 ? void 0 : columns[fallback];
52
+ if (fallback !== void 0 && fallbackColumn) set[fallback] = sql`excluded.${sql.identifier(fallbackColumn.name)}`;
53
+ return set;
54
+ };
55
+ /**
56
+ * The same values with every `undefined` key dropped, so an insert and its
57
+ * `excludedSet` agree on which columns were supplied.
58
+ */
59
+ const definedValues = (values) => Object.fromEntries(Object.entries(values).filter(([, value]) => value !== void 0));
60
+ const DEFAULT_SCHEMA = "public";
61
+ const tableOf = (value) => {
62
+ if (is(value, PgTable)) return value;
63
+ if (typeof value === "object" && value !== null && "table" in value) {
64
+ const { table } = value;
65
+ return is(table, PgTable) ? table : void 0;
66
+ }
67
+ };
68
+ /**
69
+ * The allowlist `selectFunctionPermissions` resolves a table name against,
70
+ * built from a Drizzle schema object (`import * as schema`) or the result of
71
+ * `defineRelations`. Build it once at module scope. Views and non-table
72
+ * exports are ignored; a table name that appears in two included schemas
73
+ * throws, since a bare name could not tell them apart.
74
+ */
75
+ const functionPermissionTables = (schemaObject, { schemas = [DEFAULT_SCHEMA] } = {}) => {
76
+ const tables = /* @__PURE__ */ new Map();
77
+ for (const value of Object.values(schemaObject)) {
78
+ const table = tableOf(value);
79
+ if (!table) continue;
80
+ const { name, schema = DEFAULT_SCHEMA } = getTableConfig(table);
81
+ if (!schemas.includes(schema)) continue;
82
+ const existing = tables.get(name);
83
+ if (existing !== void 0 && existing !== schema) throw new Error(`Table "${name}" exists in both "${existing}" and "${schema}"; include only one of those schemas`);
84
+ tables.set(name, schema);
85
+ }
86
+ return tables;
87
+ };
88
+ /**
89
+ * The four `<prefix>_<operation>_<table>()` answers for one table, in one
90
+ * statement: the read side of `functionPolicies`, for showing or hiding UI.
91
+ *
92
+ * `table` is only ever a map key: every identifier that reaches the statement
93
+ * comes from `tables` and is quoted with `sql.identifier`. A function missing
94
+ * or with another signature raises `42883` (`SqlState.undefinedFunction`).
95
+ * A `null` answer, which a function gives for a row the caller cannot see,
96
+ * counts as `false`, as does anything else that is not `true`.
97
+ */
98
+ const selectFunctionPermissions = async (db, { argumentName = "id", functionSchema, id, prefix = "can", table, tables }) => {
99
+ const tableSchema = tables.get(table);
100
+ if (tableSchema === void 0) throw new Error(`No permission functions for table "${table}"`);
101
+ const schema = functionSchema ?? tableSchema;
102
+ const call = (operation) => {
103
+ const callee = sql`${sql.identifier(schema)}.${sql.identifier(functionPolicyName(prefix, operation, table))}`;
104
+ return operation === "insert" || id === void 0 ? sql`${callee}()` : sql`${callee}(${sql.identifier(argumentName)} => ${id})`;
105
+ };
106
+ const row = one(await db.execute(sql`select ${sql.join(FUNCTION_POLICY_OPERATIONS.map((operation) => sql`${call(operation)} as ${sql.identifier(operation)}`), sql`, `)}`));
107
+ return {
108
+ delete: row.delete === true,
109
+ insert: row.insert === true,
110
+ select: row.select === true,
111
+ update: row.update === true
112
+ };
113
+ };
114
+ //#endregion
115
+ export { definedValues, excludedSet, functionPermissionTables, maybeOne, one, selectFunctionPermissions };
@@ -0,0 +1,158 @@
1
+ import { t as FunctionPolicyOperation } from "./function-names-hv2mj_x1.mjs";
2
+ import { AnyPgColumn, ExtraConfigColumn, HasIdentity, PgBigInt53Builder, PgBigInt64Builder, PgIntegerBuilder, PgPolicyConfig, PgTimestampBuilder, PgTimestampStringBuilder, PgUUIDBuilder, Precision, ReferenceConfig, SetHasDefault, SetIsPrimaryKey, SetNotNull, isPgEnum as isEnum, isPgMaterializedView as isMaterializedView, isPgSchema as isSchema, isPgSequence as isSequence, isPgView as isView, pgEnum as enum$1, pgMaterializedView as materializedView, pgPolicy as policy, pgRole as role, pgSequence as sequence, pgTableCreator as tableCreator, pgView as view } from "drizzle-orm/pg-core";
3
+ import { anonRole, authUid, authenticatedRole, postgresRole, realtimeMessages, realtimeTopic, serviceRole, supabaseAuthAdminRole } from "drizzle-orm/supabase";
4
+ //#region src/schema.d.ts
5
+ /** How a timestamp column is read back: a `Date` (the default) or Postgres's own text. */
6
+ type TimestampMode = "date" | "string";
7
+ type TimestampsOptions<TMode extends TimestampMode = "date"> = {
8
+ /**
9
+ * `"date"` (the default) reads a `Date`; `"string"` keeps Postgres's text
10
+ * form, which also keeps the microseconds a `Date` truncates. A JavaScript
11
+ * mapping only: it emits no SQL.
12
+ */
13
+ mode?: TMode;
14
+ withTimezone?: boolean;
15
+ /** Fractional-second digits. Postgres allows 0 to 6. */
16
+ precision?: Precision;
17
+ };
18
+ type TimestampColumn<TMode extends TimestampMode> = SetHasDefault<SetNotNull<[TMode] extends ["string"] ? PgTimestampStringBuilder : PgTimestampBuilder>>;
19
+ type TimestampColumns<TMode extends TimestampMode = "date"> = {
20
+ createdAt: TimestampColumn<TMode>;
21
+ updatedAt: TimestampColumn<TMode>;
22
+ };
23
+ declare const timestamps: <TMode extends TimestampMode = "date">({ mode, precision, withTimezone }?: TimestampsOptions<TMode>) => TimestampColumns<TMode>;
24
+ type ReferenceActions = ReferenceConfig["config"];
25
+ type AuthorColumn<TNotNull extends boolean> = [TNotNull] extends [true] ? SetNotNull<PgUUIDBuilder> : PgUUIDBuilder;
26
+ type AuthorReference = {
27
+ reference?: () => AnyPgColumn;
28
+ actions?: ReferenceActions;
29
+ } | {
30
+ reference: null;
31
+ actions?: never;
32
+ };
33
+ type AuthorshipOptions<TNotNull extends boolean = false> = AuthorReference & {
34
+ notNull?: TNotNull;
35
+ };
36
+ declare function authUserId<TNotNull extends boolean = false>(options?: AuthorshipOptions<TNotNull> & {
37
+ name?: string;
38
+ }): AuthorColumn<TNotNull>;
39
+ declare function userId<TNotNull extends boolean = false>(reference: () => AnyPgColumn, options?: {
40
+ name?: string;
41
+ actions?: ReferenceActions;
42
+ notNull?: TNotNull;
43
+ }): AuthorColumn<TNotNull>;
44
+ declare function createdBy<TNotNull extends boolean = false>(options?: AuthorshipOptions<TNotNull>): SetHasDefault<AuthorColumn<TNotNull>>;
45
+ declare function updatedBy<TNotNull extends boolean = false>(options?: AuthorshipOptions<TNotNull>): SetHasDefault<AuthorColumn<TNotNull>>;
46
+ declare const authorship: <TNotNull extends boolean = false>(options?: AuthorshipOptions<TNotNull>) => {
47
+ createdBy: SetHasDefault<AuthorColumn<TNotNull>>;
48
+ updatedBy: SetHasDefault<AuthorColumn<TNotNull>>;
49
+ };
50
+ declare const auditColumns: <TNotNull extends boolean = false, TMode extends TimestampMode = "date">(options?: AuthorshipOptions<TNotNull> & TimestampsOptions<TMode>) => {
51
+ createdAt: TimestampColumn<TMode>;
52
+ updatedAt: TimestampColumn<TMode>;
53
+ createdBy: SetHasDefault<AuthorColumn<TNotNull>>;
54
+ updatedBy: SetHasDefault<AuthorColumn<TNotNull>>;
55
+ };
56
+ type UuidPrimaryIdColumn<TDefaultRandom extends boolean> = [TDefaultRandom] extends [false] ? SetIsPrimaryKey<PgUUIDBuilder> : SetHasDefault<SetIsPrimaryKey<PgUUIDBuilder>>;
57
+ declare function uuidPrimaryId<TDefaultRandom extends boolean = true>(options?: {
58
+ name?: string;
59
+ defaultRandom?: TDefaultRandom;
60
+ }): UuidPrimaryIdColumn<TDefaultRandom>;
61
+ type IdentityType = "integer" | "bigint";
62
+ type IdentityMode = "number" | "bigint";
63
+ type IdentityGeneration = "byDefault" | "always";
64
+ type IdentityBuilder<TType extends IdentityType, TMode extends IdentityMode> = TType extends "integer" ? PgIntegerBuilder : TMode extends "bigint" ? PgBigInt64Builder : PgBigInt53Builder;
65
+ type SequentialPrimaryIdColumn<TType extends IdentityType, TMode extends IdentityMode, TGeneration extends IdentityGeneration> = HasIdentity<SetIsPrimaryKey<IdentityBuilder<TType, TMode>>, TGeneration>;
66
+ declare function sequentialPrimaryId<TType extends IdentityType = "bigint", TMode extends IdentityMode = "number", TGeneration extends IdentityGeneration = "byDefault">(options?: {
67
+ name?: string;
68
+ type?: TType;
69
+ mode?: TType extends "integer" ? never : TMode;
70
+ generated?: TGeneration;
71
+ }): SequentialPrimaryIdColumn<TType, TMode, TGeneration>;
72
+ declare const assignedPrimaryId: ({ name, length }?: {
73
+ name?: string;
74
+ length?: number;
75
+ }) => SetIsPrimaryKey<import("drizzle-orm/pg-core").PgVarcharBuilder<[string, ...string[]]>>;
76
+ type PrimaryIdKind = "uuid" | "sequential" | "assigned";
77
+ type PrimaryIdColumn<TKind extends PrimaryIdKind> = TKind extends "uuid" ? UuidPrimaryIdColumn<true> : TKind extends "sequential" ? SequentialPrimaryIdColumn<"bigint", "number", "byDefault"> : ReturnType<typeof assignedPrimaryId>;
78
+ declare function primaryId<TKind extends PrimaryIdKind = "sequential">(kind?: TKind): PrimaryIdColumn<TKind>;
79
+ type PolicyOptions = Omit<PgPolicyConfig, "for">;
80
+ type PolicyOperation = NonNullable<PgPolicyConfig["for"]>;
81
+ type AuthenticatedPolicyOptions = Omit<PgPolicyConfig, "for" | "to">;
82
+ declare const selectPolicy: (name: string, config?: PolicyOptions) => import("drizzle-orm/pg-core").PgPolicy;
83
+ declare const insertPolicy: (name: string, config?: PolicyOptions) => import("drizzle-orm/pg-core").PgPolicy;
84
+ declare const updatePolicy: (name: string, config?: PolicyOptions) => import("drizzle-orm/pg-core").PgPolicy;
85
+ declare const deletePolicy: (name: string, config?: PolicyOptions) => import("drizzle-orm/pg-core").PgPolicy;
86
+ declare const allPolicy: (name: string, config?: PolicyOptions) => import("drizzle-orm/pg-core").PgPolicy;
87
+ declare const authenticatedSelectPolicy: (name: string, config?: AuthenticatedPolicyOptions) => import("drizzle-orm/pg-core").PgPolicy;
88
+ declare const authenticatedInsertPolicy: (name: string, config?: AuthenticatedPolicyOptions) => import("drizzle-orm/pg-core").PgPolicy;
89
+ declare const authenticatedUpdatePolicy: (name: string, config?: AuthenticatedPolicyOptions) => import("drizzle-orm/pg-core").PgPolicy;
90
+ declare const authenticatedDeletePolicy: (name: string, config?: AuthenticatedPolicyOptions) => import("drizzle-orm/pg-core").PgPolicy;
91
+ declare const authenticatedAllPolicy: (name: string, config?: AuthenticatedPolicyOptions) => import("drizzle-orm/pg-core").PgPolicy;
92
+ /** Columns one function is called with. `null` calls it with none. */
93
+ type FunctionArgument = AnyPgColumn | readonly AnyPgColumn[] | null;
94
+ type FunctionPoliciesOptions = {
95
+ /**
96
+ * Passed to each function, e.g. the row's id: one column, an array for a
97
+ * function taking several, or a record to vary them per operation. The
98
+ * common shape is that the row-scoped operations take the row and `insert`
99
+ * takes nothing, there being no row yet to authorise, only the caller. An
100
+ * operation missing from the record, or set to `null`, is called with no
101
+ * arguments; so is every operation when `argument` is omitted.
102
+ */
103
+ argument?: FunctionArgument | Partial<Record<FunctionPolicyOperation, FunctionArgument>>;
104
+ /**
105
+ * Schema the functions live in, e.g. `"billing"` for
106
+ * `billing.can_select_invoices`. Omit to emit the name unqualified and let
107
+ * the `search_path` in effect resolve it.
108
+ */
109
+ schema?: string;
110
+ /** Prefix for the default function and policy names. Default `"can"`. */
111
+ prefix?: string;
112
+ /** Overrides the generated policy name. */
113
+ name?: (operation: PolicyOperation, table: string) => string;
114
+ };
115
+ type ExtraConfigColumns = Record<string, ExtraConfigColumn>;
116
+ /**
117
+ * One policy per operation, each delegating to a `security definer` function.
118
+ *
119
+ * Access rarely depends only on the row's own owner column, and the usual
120
+ * answer is a function, which is also the standard advice for keeping RLS
121
+ * predicates out of the planner's way:
122
+ *
123
+ * ```ts
124
+ * table("posts", { id: primaryId("uuid") }, (t) =>
125
+ * functionPolicies(t, { argument: t.id })
126
+ * )
127
+ * ```
128
+ *
129
+ * ```sql
130
+ * CREATE POLICY "can_select_posts" ON "posts" FOR SELECT TO "authenticated"
131
+ * USING ((select "can_select_posts"("posts"."id")));
132
+ * ```
133
+ *
134
+ * `argument` also takes a record, for the usual shape where `insert` has no
135
+ * row to authorise yet:
136
+ *
137
+ * ```ts
138
+ * functionPolicies(t, {
139
+ * argument: { delete: t.id, select: t.id, update: t.id },
140
+ * })
141
+ * ```
142
+ */
143
+ declare const functionPolicies: (columns: ExtraConfigColumns, { argument, name, prefix, schema: functionSchema }?: FunctionPoliciesOptions) => import("drizzle-orm/pg-core").PgPolicy[];
144
+ declare const authUserOwns: (ownerColumn: AnyPgColumn) => import("drizzle-orm").SQL<unknown>;
145
+ type OwnerPolicyOptions = Omit<PgPolicyConfig, "for" | "to">;
146
+ declare const authenticatedOwnerSelectPolicy: (name: string, ownerColumn: AnyPgColumn, config?: OwnerPolicyOptions) => import("drizzle-orm/pg-core").PgPolicy;
147
+ declare const authenticatedOwnerInsertPolicy: (name: string, ownerColumn: AnyPgColumn, config?: OwnerPolicyOptions) => import("drizzle-orm/pg-core").PgPolicy;
148
+ declare const authenticatedOwnerUpdatePolicy: (name: string, ownerColumn: AnyPgColumn, config?: OwnerPolicyOptions) => import("drizzle-orm/pg-core").PgPolicy;
149
+ declare const authenticatedOwnerDeletePolicy: (name: string, ownerColumn: AnyPgColumn, config?: OwnerPolicyOptions) => import("drizzle-orm/pg-core").PgPolicy;
150
+ declare const authenticatedOwnerAllPolicy: (name: string, ownerColumn: AnyPgColumn, config?: OwnerPolicyOptions) => import("drizzle-orm/pg-core").PgPolicy;
151
+ declare const table: import("drizzle-orm/pg-core").PgTableFnInternal<undefined>;
152
+ declare const unsecureTable: import("drizzle-orm/pg-core").PgTableFn<undefined>;
153
+ declare const schema: <TName extends string>(name: TName) => import("drizzle-orm/pg-core").PgSchema<TName> & {
154
+ table: import("drizzle-orm/pg-core").PgTableFnInternal<TName>;
155
+ unsecureTable: import("drizzle-orm/pg-core").PgTableFn<TName>;
156
+ };
157
+ //#endregion
158
+ export { AuthorshipOptions, TimestampColumns, TimestampMode, TimestampsOptions, allPolicy, anonRole, assignedPrimaryId, auditColumns, authUid, authUserId, authUserOwns, authenticatedAllPolicy, authenticatedDeletePolicy, authenticatedInsertPolicy, authenticatedOwnerAllPolicy, authenticatedOwnerDeletePolicy, authenticatedOwnerInsertPolicy, authenticatedOwnerSelectPolicy, authenticatedOwnerUpdatePolicy, authenticatedRole, authenticatedSelectPolicy, authenticatedUpdatePolicy, authorship, createdBy, deletePolicy, enum$1 as enum, functionPolicies, insertPolicy, isEnum, isMaterializedView, isSchema, isSequence, isView, materializedView, policy, postgresRole, primaryId, realtimeMessages, realtimeTopic, role, schema, selectPolicy, sequence, sequentialPrimaryId, serviceRole, supabaseAuthAdminRole, table, tableCreator, timestamps, unsecureTable, updatePolicy, updatedBy, userId, uuidPrimaryId, view };
@@ -0,0 +1,219 @@
1
+ import { authUsers } from "./auth-schema.mjs";
2
+ import { n as functionPolicyName, t as FUNCTION_POLICY_OPERATIONS } from "./function-names-BDc-AuFm.mjs";
3
+ import { Column, getColumnTable, getTableName, is, sql } from "drizzle-orm";
4
+ import { bigint, integer, isPgEnum as isEnum, isPgMaterializedView as isMaterializedView, isPgSchema as isSchema, isPgSequence as isSequence, isPgView as isView, pgEnum as enum$1, pgMaterializedView as materializedView, pgPolicy, pgPolicy as policy, pgRole as role, pgSequence as sequence, pgTableCreator as tableCreator, pgView as view, timestamp, uuid, varchar } from "drizzle-orm/pg-core";
5
+ import { snakeCase } from "drizzle-orm/pg-core/casing";
6
+ import { anonRole, authUid, authUid as authUid$1, authenticatedRole, authenticatedRole as authenticatedRole$1, postgresRole, realtimeMessages, realtimeTopic, serviceRole, supabaseAuthAdminRole } from "drizzle-orm/supabase";
7
+ //#region src/schema.ts
8
+ const timestamps = ({ mode, precision, withTimezone = true } = {}) => {
9
+ const column = (name) => mode === "string" ? timestamp(name, {
10
+ mode: "string",
11
+ precision,
12
+ withTimezone
13
+ }).notNull().defaultNow() : timestamp(name, {
14
+ precision,
15
+ withTimezone
16
+ }).notNull().defaultNow();
17
+ return {
18
+ createdAt: column("created_at"),
19
+ updatedAt: column("updated_at")
20
+ };
21
+ };
22
+ const NULLABLE_AUTHOR_ACTIONS = {
23
+ onDelete: "set null",
24
+ onUpdate: "cascade"
25
+ };
26
+ const REQUIRED_AUTHOR_ACTIONS = {
27
+ onDelete: "restrict",
28
+ onUpdate: "cascade"
29
+ };
30
+ const authorColumn = ({ name, reference = () => authUsers.id, actions, notNull = false }) => {
31
+ const column = reference === null ? uuid(name) : uuid(name).references(reference, actions ?? (notNull ? REQUIRED_AUTHOR_ACTIONS : NULLABLE_AUTHOR_ACTIONS));
32
+ return notNull ? column.notNull() : column;
33
+ };
34
+ function authUserId(options = {}) {
35
+ return authorColumn(options);
36
+ }
37
+ function userId(reference, options = {}) {
38
+ return authorColumn({
39
+ ...options,
40
+ reference
41
+ });
42
+ }
43
+ const AUTH_UID_DEFAULT = sql`auth.uid()`;
44
+ function createdBy(options = {}) {
45
+ return authorColumn({
46
+ ...options,
47
+ name: "created_by"
48
+ }).default(AUTH_UID_DEFAULT);
49
+ }
50
+ function updatedBy(options = {}) {
51
+ return authorColumn({
52
+ ...options,
53
+ name: "updated_by"
54
+ }).default(AUTH_UID_DEFAULT);
55
+ }
56
+ const authorship = (options) => ({
57
+ createdBy: createdBy(options),
58
+ updatedBy: updatedBy(options)
59
+ });
60
+ const auditColumns = (options = {}) => ({
61
+ ...timestamps(options),
62
+ ...authorship(options)
63
+ });
64
+ const DEFAULT_ID_NAME = "id";
65
+ function uuidPrimaryId(options = {}) {
66
+ const { name = DEFAULT_ID_NAME, defaultRandom = true } = options;
67
+ const column = uuid(name).primaryKey();
68
+ return defaultRandom ? column.defaultRandom() : column;
69
+ }
70
+ function sequentialPrimaryId(options = {}) {
71
+ const { name = DEFAULT_ID_NAME, type = "bigint", mode = "number", generated = "byDefault" } = options;
72
+ const column = type === "integer" ? integer(name).primaryKey() : bigint(name, { mode }).primaryKey();
73
+ return generated === "always" ? column.generatedAlwaysAsIdentity() : column.generatedByDefaultAsIdentity();
74
+ }
75
+ const assignedPrimaryId = ({ name = DEFAULT_ID_NAME, length } = {}) => varchar(name, { length }).primaryKey();
76
+ function primaryId(kind = "sequential") {
77
+ if (kind === "uuid") return uuidPrimaryId();
78
+ if (kind === "assigned") return assignedPrimaryId();
79
+ return sequentialPrimaryId();
80
+ }
81
+ function operationPolicy(name, operation, config = {}) {
82
+ return pgPolicy(name, {
83
+ ...config,
84
+ for: operation
85
+ });
86
+ }
87
+ const selectPolicy = (name, config = {}) => operationPolicy(name, "select", config);
88
+ const insertPolicy = (name, config = {}) => operationPolicy(name, "insert", config);
89
+ const updatePolicy = (name, config = {}) => operationPolicy(name, "update", config);
90
+ const deletePolicy = (name, config = {}) => operationPolicy(name, "delete", config);
91
+ const allPolicy = (name, config = {}) => operationPolicy(name, "all", config);
92
+ const authenticatedSelectPolicy = (name, config = {}) => selectPolicy(name, {
93
+ ...config,
94
+ to: authenticatedRole$1
95
+ });
96
+ const authenticatedInsertPolicy = (name, config = {}) => insertPolicy(name, {
97
+ ...config,
98
+ to: authenticatedRole$1
99
+ });
100
+ const authenticatedUpdatePolicy = (name, config = {}) => updatePolicy(name, {
101
+ ...config,
102
+ to: authenticatedRole$1
103
+ });
104
+ const authenticatedDeletePolicy = (name, config = {}) => deletePolicy(name, {
105
+ ...config,
106
+ to: authenticatedRole$1
107
+ });
108
+ const authenticatedAllPolicy = (name, config = {}) => allPolicy(name, {
109
+ ...config,
110
+ to: authenticatedRole$1
111
+ });
112
+ const POLICY_CLAUSE = {
113
+ all: "using",
114
+ delete: "using",
115
+ insert: "withCheck",
116
+ select: "using",
117
+ update: "using"
118
+ };
119
+ const POLICY_BUILDERS = {
120
+ all: allPolicy,
121
+ delete: deletePolicy,
122
+ insert: insertPolicy,
123
+ select: selectPolicy,
124
+ update: updatePolicy
125
+ };
126
+ const isWholeSetArgument = (argument) => is(argument, Column) || Array.isArray(argument);
127
+ const argumentsFor = (argument, operation) => {
128
+ if (!argument) return [];
129
+ const forOperation = isWholeSetArgument(argument) ? argument : argument[operation];
130
+ if (!forOperation) return [];
131
+ return is(forOperation, Column) ? [forOperation] : [...forOperation];
132
+ };
133
+ /**
134
+ * One policy per operation, each delegating to a `security definer` function.
135
+ *
136
+ * Access rarely depends only on the row's own owner column, and the usual
137
+ * answer is a function, which is also the standard advice for keeping RLS
138
+ * predicates out of the planner's way:
139
+ *
140
+ * ```ts
141
+ * table("posts", { id: primaryId("uuid") }, (t) =>
142
+ * functionPolicies(t, { argument: t.id })
143
+ * )
144
+ * ```
145
+ *
146
+ * ```sql
147
+ * CREATE POLICY "can_select_posts" ON "posts" FOR SELECT TO "authenticated"
148
+ * USING ((select "can_select_posts"("posts"."id")));
149
+ * ```
150
+ *
151
+ * `argument` also takes a record, for the usual shape where `insert` has no
152
+ * row to authorise yet:
153
+ *
154
+ * ```ts
155
+ * functionPolicies(t, {
156
+ * argument: { delete: t.id, select: t.id, update: t.id },
157
+ * })
158
+ * ```
159
+ */
160
+ const functionPolicies = (columns, { argument, name, prefix = "can", schema: functionSchema } = {}) => {
161
+ const [firstColumn] = Object.values(columns);
162
+ if (!firstColumn) throw new Error("functionPolicies needs a table with at least one column");
163
+ const tableName = getTableName(getColumnTable(firstColumn));
164
+ return FUNCTION_POLICY_OPERATIONS.map((operation) => {
165
+ const functionName = functionPolicyName(prefix, operation, tableName);
166
+ const callee = functionSchema ? sql`${sql.identifier(functionSchema)}.${sql.identifier(functionName)}` : sql`${sql.identifier(functionName)}`;
167
+ const condition = sql`(select ${callee}(${sql.join(argumentsFor(argument, operation), sql`, `)}))`;
168
+ const clause = { [POLICY_CLAUSE[operation]]: condition };
169
+ return POLICY_BUILDERS[operation](name?.(operation, tableName) ?? functionName, {
170
+ to: authenticatedRole$1,
171
+ ...clause
172
+ });
173
+ });
174
+ };
175
+ const authUserOwns = (ownerColumn) => sql`${ownerColumn} = ${authUid$1}`;
176
+ const authenticatedOwnerSelectPolicy = (name, ownerColumn, config = {}) => selectPolicy(name, {
177
+ ...config,
178
+ to: authenticatedRole$1,
179
+ using: config.using ?? authUserOwns(ownerColumn)
180
+ });
181
+ const authenticatedOwnerInsertPolicy = (name, ownerColumn, config = {}) => insertPolicy(name, {
182
+ ...config,
183
+ to: authenticatedRole$1,
184
+ withCheck: config.withCheck ?? authUserOwns(ownerColumn)
185
+ });
186
+ const authenticatedOwnerUpdatePolicy = (name, ownerColumn, config = {}) => {
187
+ const ownerCondition = authUserOwns(ownerColumn);
188
+ return updatePolicy(name, {
189
+ ...config,
190
+ to: authenticatedRole$1,
191
+ using: config.using ?? ownerCondition,
192
+ withCheck: config.withCheck ?? ownerCondition
193
+ });
194
+ };
195
+ const authenticatedOwnerDeletePolicy = (name, ownerColumn, config = {}) => deletePolicy(name, {
196
+ ...config,
197
+ to: authenticatedRole$1,
198
+ using: config.using ?? authUserOwns(ownerColumn)
199
+ });
200
+ const authenticatedOwnerAllPolicy = (name, ownerColumn, config = {}) => {
201
+ const ownerCondition = authUserOwns(ownerColumn);
202
+ return allPolicy(name, {
203
+ ...config,
204
+ to: authenticatedRole$1,
205
+ using: config.using ?? ownerCondition,
206
+ withCheck: config.withCheck ?? ownerCondition
207
+ });
208
+ };
209
+ const table = snakeCase.table.withRLS;
210
+ const unsecureTable = snakeCase.table;
211
+ const schema = (name) => {
212
+ const built = snakeCase.schema(name);
213
+ return Object.assign(built, {
214
+ table: built.table.withRLS,
215
+ unsecureTable: built.table
216
+ });
217
+ };
218
+ //#endregion
219
+ export { allPolicy, anonRole, assignedPrimaryId, auditColumns, authUid, authUserId, authUserOwns, authenticatedAllPolicy, authenticatedDeletePolicy, authenticatedInsertPolicy, authenticatedOwnerAllPolicy, authenticatedOwnerDeletePolicy, authenticatedOwnerInsertPolicy, authenticatedOwnerSelectPolicy, authenticatedOwnerUpdatePolicy, authenticatedRole, authenticatedSelectPolicy, authenticatedUpdatePolicy, authorship, createdBy, deletePolicy, enum$1 as enum, functionPolicies, insertPolicy, isEnum, isMaterializedView, isSchema, isSequence, isView, materializedView, policy, postgresRole, primaryId, realtimeMessages, realtimeTopic, role, schema, selectPolicy, sequence, sequentialPrimaryId, serviceRole, supabaseAuthAdminRole, table, tableCreator, timestamps, unsecureTable, updatePolicy, updatedBy, userId, uuidPrimaryId, view };
@@ -0,0 +1,65 @@
1
+ import { PgTable } from "drizzle-orm/pg-core";
2
+ //#region src/triggers.d.ts
3
+ type UpdatedAtTriggerOptions = {
4
+ /** Column the trigger maintains. Default `"updated_at"`. */
5
+ column?: string;
6
+ /** Schema holding the table. Default `"public"`, or the table's own. */
7
+ schema?: string;
8
+ /** Trigger name. Default `handle_<column>`. */
9
+ name?: string;
10
+ /** Schema `moddatetime` is installed into. Default `"extensions"`. */
11
+ extensionSchema?: string;
12
+ /** Emit `create extension if not exists`. Default `true`. */
13
+ createExtension?: boolean;
14
+ };
15
+ /**
16
+ * `CREATE TRIGGER` SQL that keeps `updated_at` current for **every** writer,
17
+ * including PostgREST, the dashboard and psql.
18
+ *
19
+ * `timestamps()` gives the column a `DEFAULT now()`, which covers the insert.
20
+ * SQL has no "on update" default, so without this trigger the column is only
21
+ * ever the insert time. Required, not optional.
22
+ *
23
+ * ```ts
24
+ * // pnpm db:generate --custom --name=posts_audit_triggers
25
+ * // then paste the output into the generated migration:
26
+ * console.log(updatedAtTrigger(posts))
27
+ * ```
28
+ */
29
+ declare function updatedAtTrigger(table: PgTable | string, { column, createExtension, extensionSchema, name, schema }?: UpdatedAtTriggerOptions): string;
30
+ /** `create extension if not exists moddatetime`, safe to repeat per migration. */
31
+ declare function moddatetimeExtension(schema?: string): string;
32
+ type UpdatedByTriggerOptions = {
33
+ /** Column the trigger maintains. Default `"updated_by"`. */
34
+ column?: string;
35
+ /** Schema holding the table. Default `"public"`, or the table's own. */
36
+ schema?: string;
37
+ /** Trigger name. Default `handle_<column>`. */
38
+ name?: string;
39
+ /** Schema the helper function is created in. Default `"public"`. */
40
+ functionSchema?: string;
41
+ /** Emit `create or replace function`. Default `true`. */
42
+ createFunction?: boolean;
43
+ };
44
+ /**
45
+ * `CREATE TRIGGER` SQL that stamps an author column with `auth.uid()` on every
46
+ * update, the `updated_at` story for `updated_by`.
47
+ *
48
+ * `updatedBy()` gives the column a `DEFAULT (select auth.uid())`, which covers
49
+ * the insert. This covers the update, for every writer rather than only the
50
+ * ones going through Drizzle.
51
+ *
52
+ * A write with no session (the admin or `service_role` clients) records `NULL`,
53
+ * because that is what `auth.uid()` returns there.
54
+ */
55
+ declare function updatedByTrigger(table: PgTable | string, { column, createFunction, functionSchema, name, schema }?: UpdatedByTriggerOptions): string;
56
+ /**
57
+ * Both update triggers `auditColumns()` needs, in one string.
58
+ *
59
+ * `auditColumns()` and `timestamps()` leave `updated_at` and `updated_by` at
60
+ * their insert values without these, so a table using either helper wants this
61
+ * in a `drizzle-kit generate --custom` migration.
62
+ */
63
+ declare function auditTriggers(table: PgTable | string, options?: UpdatedAtTriggerOptions & UpdatedByTriggerOptions): string;
64
+ //#endregion
65
+ export { auditTriggers, moddatetimeExtension, updatedAtTrigger, updatedByTrigger };