@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.
- package/dist/auth-schema.d.mts +543 -0
- package/dist/auth-schema.mjs +50 -0
- package/dist/clients-BlUEH7QJ.d.mts +58 -0
- package/dist/clients-CdPr3mCZ.mjs +197 -0
- package/dist/config.d.mts +20 -0
- package/dist/config.mjs +64 -0
- package/dist/errors.d.mts +70 -0
- package/dist/errors.mjs +78 -0
- package/dist/function-names-BDc-AuFm.mjs +10 -0
- package/dist/function-names-hv2mj_x1.d.mts +5 -0
- package/dist/index.d.mts +2 -0
- package/dist/index.mjs +2 -0
- package/dist/next.d.mts +87 -0
- package/dist/next.mjs +69 -0
- package/dist/query.d.mts +103 -0
- package/dist/query.mjs +115 -0
- package/dist/schema.d.mts +158 -0
- package/dist/schema.mjs +219 -0
- package/dist/triggers.d.mts +65 -0
- package/dist/triggers.mjs +93 -0
- package/package.json +29 -5
- package/src/casing.test.ts +43 -20
- package/src/clients.ts +60 -5
- package/src/database-url.test.ts +88 -0
- package/src/define-action.ts +63 -0
- package/src/errors.test.ts +95 -0
- package/src/errors.ts +133 -0
- package/src/function-names.ts +21 -0
- package/src/index.ts +2 -0
- package/src/next.test-d.ts +32 -0
- package/src/next.test.ts +178 -0
- package/src/next.ts +112 -0
- package/src/query.test-d.ts +20 -0
- package/src/query.test.ts +261 -0
- package/src/query.ts +252 -0
- package/src/schema.test-d.ts +24 -0
- package/src/schema.ts +51 -21
package/dist/query.d.mts
ADDED
|
@@ -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 };
|
package/dist/schema.mjs
ADDED
|
@@ -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 };
|