@zeno-lib/db 0.3.0 → 0.3.2
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/config.d.mts +20 -0
- package/dist/config.mjs +64 -0
- package/dist/index.d.mts +42 -0
- package/dist/index.mjs +167 -0
- package/dist/schema.d.mts +149 -0
- package/dist/schema.mjs +220 -0
- package/dist/triggers.d.mts +65 -0
- package/dist/triggers.mjs +93 -0
- package/package.json +30 -6
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { getTableName, is } from "drizzle-orm";
|
|
2
|
+
import { PgTable, getTableConfig } from "drizzle-orm/pg-core";
|
|
3
|
+
//#region src/triggers.ts
|
|
4
|
+
const DEFAULT_EXTENSION_SCHEMA = "extensions";
|
|
5
|
+
const DEFAULT_COLUMN = "updated_at";
|
|
6
|
+
const DEFAULT_AUTHOR_COLUMN = "updated_by";
|
|
7
|
+
const DEFAULT_TABLE_SCHEMA = "public";
|
|
8
|
+
const quote = (identifier) => `"${identifier.split("\"").join("\"\"")}"`;
|
|
9
|
+
function resolveTable(table, schema) {
|
|
10
|
+
const name = is(table, PgTable) ? getTableName(table) : table;
|
|
11
|
+
const tableSchema = schema ?? (is(table, PgTable) ? getTableConfig(table).schema ?? DEFAULT_TABLE_SCHEMA : DEFAULT_TABLE_SCHEMA);
|
|
12
|
+
return { qualified: `${quote(tableSchema)}.${quote(name)}` };
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* `CREATE TRIGGER` SQL that keeps `updated_at` current for **every** writer,
|
|
16
|
+
* including PostgREST, the dashboard and psql.
|
|
17
|
+
*
|
|
18
|
+
* `timestamps()` gives the column a `DEFAULT now()`, which covers the insert.
|
|
19
|
+
* SQL has no "on update" default, so without this trigger the column is only
|
|
20
|
+
* ever the insert time. Required, not optional.
|
|
21
|
+
*
|
|
22
|
+
* ```ts
|
|
23
|
+
* // pnpm db:generate --custom --name=posts_audit_triggers
|
|
24
|
+
* // then paste the output into the generated migration:
|
|
25
|
+
* console.log(updatedAtTrigger(posts))
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
function updatedAtTrigger(table, { column = DEFAULT_COLUMN, createExtension = true, extensionSchema = DEFAULT_EXTENSION_SCHEMA, name, schema } = {}) {
|
|
29
|
+
const { qualified } = resolveTable(table, schema);
|
|
30
|
+
const triggerName = name ?? `handle_${column}`;
|
|
31
|
+
const trigger = [
|
|
32
|
+
`drop trigger if exists ${quote(triggerName)} on ${qualified};`,
|
|
33
|
+
`create trigger ${quote(triggerName)}`,
|
|
34
|
+
` before update on ${qualified}`,
|
|
35
|
+
" for each row",
|
|
36
|
+
` execute function ${quote(extensionSchema)}.moddatetime (${quote(column)});`
|
|
37
|
+
].join("\n");
|
|
38
|
+
return createExtension ? `${moddatetimeExtension(extensionSchema)}\n\n${trigger}` : trigger;
|
|
39
|
+
}
|
|
40
|
+
/** `create extension if not exists moddatetime`, safe to repeat per migration. */
|
|
41
|
+
function moddatetimeExtension(schema = DEFAULT_EXTENSION_SCHEMA) {
|
|
42
|
+
return `create extension if not exists moddatetime with schema ${quote(schema)};`;
|
|
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
|
+
function updatedByTrigger(table, { column = DEFAULT_AUTHOR_COLUMN, createFunction = true, functionSchema = DEFAULT_TABLE_SCHEMA, name, schema } = {}) {
|
|
56
|
+
const { qualified } = resolveTable(table, schema);
|
|
57
|
+
const triggerName = name ?? `handle_${column}`;
|
|
58
|
+
const functionName = `set_${column}`;
|
|
59
|
+
const qualifiedFunction = `${quote(functionSchema)}.${quote(functionName)}`;
|
|
60
|
+
const routine = [
|
|
61
|
+
`create or replace function ${qualifiedFunction}()`,
|
|
62
|
+
" returns trigger",
|
|
63
|
+
" language plpgsql",
|
|
64
|
+
" security invoker",
|
|
65
|
+
` set search_path = ''`,
|
|
66
|
+
"as $$",
|
|
67
|
+
"begin",
|
|
68
|
+
` new.${quote(column)} = (select auth.uid());`,
|
|
69
|
+
" return new;",
|
|
70
|
+
"end;",
|
|
71
|
+
"$$;"
|
|
72
|
+
].join("\n");
|
|
73
|
+
const trigger = [
|
|
74
|
+
`drop trigger if exists ${quote(triggerName)} on ${qualified};`,
|
|
75
|
+
`create trigger ${quote(triggerName)}`,
|
|
76
|
+
` before update on ${qualified}`,
|
|
77
|
+
" for each row",
|
|
78
|
+
` execute function ${qualifiedFunction}();`
|
|
79
|
+
].join("\n");
|
|
80
|
+
return createFunction ? `${routine}\n\n${trigger}` : trigger;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Both update triggers `auditColumns()` needs, in one string.
|
|
84
|
+
*
|
|
85
|
+
* `auditColumns()` and `timestamps()` leave `updated_at` and `updated_by` at
|
|
86
|
+
* their insert values without these, so a table using either helper wants this
|
|
87
|
+
* in a `drizzle-kit generate --custom` migration.
|
|
88
|
+
*/
|
|
89
|
+
function auditTriggers(table, options = {}) {
|
|
90
|
+
return `${updatedAtTrigger(table, options)}\n\n${updatedByTrigger(table, options)}`;
|
|
91
|
+
}
|
|
92
|
+
//#endregion
|
|
93
|
+
export { auditTriggers, moddatetimeExtension, updatedAtTrigger, updatedByTrigger };
|
package/package.json
CHANGED
|
@@ -1,15 +1,36 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zeno-lib/db",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
|
-
".":
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
".": {
|
|
7
|
+
"types": "./dist/index.d.mts",
|
|
8
|
+
"import": "./dist/index.mjs",
|
|
9
|
+
"default": "./dist/index.mjs"
|
|
10
|
+
},
|
|
11
|
+
"./auth": {
|
|
12
|
+
"types": "./dist/auth-schema.d.mts",
|
|
13
|
+
"import": "./dist/auth-schema.mjs",
|
|
14
|
+
"default": "./dist/auth-schema.mjs"
|
|
15
|
+
},
|
|
16
|
+
"./config": {
|
|
17
|
+
"types": "./dist/config.d.mts",
|
|
18
|
+
"import": "./dist/config.mjs",
|
|
19
|
+
"default": "./dist/config.mjs"
|
|
20
|
+
},
|
|
21
|
+
"./schema": {
|
|
22
|
+
"types": "./dist/schema.d.mts",
|
|
23
|
+
"import": "./dist/schema.mjs",
|
|
24
|
+
"default": "./dist/schema.mjs"
|
|
25
|
+
},
|
|
26
|
+
"./triggers": {
|
|
27
|
+
"types": "./dist/triggers.d.mts",
|
|
28
|
+
"import": "./dist/triggers.mjs",
|
|
29
|
+
"default": "./dist/triggers.mjs"
|
|
30
|
+
}
|
|
11
31
|
},
|
|
12
32
|
"files": [
|
|
33
|
+
"dist",
|
|
13
34
|
"src"
|
|
14
35
|
],
|
|
15
36
|
"publishConfig": {
|
|
@@ -33,12 +54,15 @@
|
|
|
33
54
|
"drizzle-orm": "1.0.0-rc.3",
|
|
34
55
|
"postgres": "3.4.7",
|
|
35
56
|
"supabase": "2.84.1",
|
|
57
|
+
"tsdown": "^0.22.14",
|
|
58
|
+
"typescript": "7.0.2",
|
|
36
59
|
"vite": "8.0.16",
|
|
37
60
|
"vitest": "4.1.0",
|
|
38
61
|
"@zeno-lib/test": "0.0.3",
|
|
39
62
|
"@zeno-lib/typescript": "^1.1.0"
|
|
40
63
|
},
|
|
41
64
|
"scripts": {
|
|
65
|
+
"build": "tsdown",
|
|
42
66
|
"dev": "pnpm exec supabase start",
|
|
43
67
|
"stop": "pnpm exec supabase stop",
|
|
44
68
|
"reset": "pnpm exec supabase db reset",
|