prostgles-server 4.2.605 → 4.2.607
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/README.md +29 -0
- package/dist/Audit/AuditTable.d.ts +18 -0
- package/dist/Audit/AuditTable.d.ts.map +1 -0
- package/dist/Audit/AuditTable.js +18 -0
- package/dist/Audit/AuditTable.js.map +1 -0
- package/dist/Audit/AuditTypes.d.ts +25 -0
- package/dist/Audit/AuditTypes.d.ts.map +1 -0
- package/dist/Audit/AuditTypes.js +3 -0
- package/dist/Audit/AuditTypes.js.map +1 -0
- package/dist/Audit/getAuditTableConfig.d.ts +13 -0
- package/dist/Audit/getAuditTableConfig.d.ts.map +1 -0
- package/dist/Audit/getAuditTableConfig.js +54 -0
- package/dist/Audit/getAuditTableConfig.js.map +1 -0
- package/dist/Audit/parseAuditConfig.d.ts +5 -0
- package/dist/Audit/parseAuditConfig.d.ts.map +1 -0
- package/dist/Audit/parseAuditConfig.js +123 -0
- package/dist/Audit/parseAuditConfig.js.map +1 -0
- package/dist/DboBuilder/DboBuilder.js +2 -2
- package/dist/DboBuilder/DboBuilder.js.map +1 -1
- package/dist/DboBuilder/ViewHandler/ViewHandler.d.ts +1 -1
- package/dist/DboBuilder/ViewHandler/getInfo.d.ts.map +1 -1
- package/dist/DboBuilder/ViewHandler/getInfo.js +5 -46
- package/dist/DboBuilder/ViewHandler/getInfo.js.map +1 -1
- package/dist/DboBuilder/ViewHandler/getRawInfo.d.ts +7 -0
- package/dist/DboBuilder/ViewHandler/getRawInfo.d.ts.map +1 -0
- package/dist/DboBuilder/ViewHandler/getRawInfo.js +51 -0
- package/dist/DboBuilder/ViewHandler/getRawInfo.js.map +1 -0
- package/dist/DboBuilder/getColumns.d.ts +5 -0
- package/dist/DboBuilder/getColumns.d.ts.map +1 -1
- package/dist/DboBuilder/getColumns.js +80 -74
- package/dist/DboBuilder/getColumns.js.map +1 -1
- package/dist/DboBuilder/runSql/QueryStreamer.d.ts.map +1 -1
- package/dist/DboBuilder/runSql/QueryStreamer.js +1 -0
- package/dist/DboBuilder/runSql/QueryStreamer.js.map +1 -1
- package/dist/DboBuilder/schema/getTablesForSchemaPostgresSQL.d.ts +3 -3
- package/dist/DboBuilder/schema/getTablesForSchemaPostgresSQL.d.ts.map +1 -1
- package/dist/DboBuilder/schema/getTablesForSchemaPostgresSQL.js +2 -2
- package/dist/DboBuilder/schema/getTablesForSchemaPostgresSQL.js.map +1 -1
- package/dist/DboBuilder/schema/getViewReferenceColumns.d.ts +2 -2
- package/dist/DboBuilder/schema/getViewReferenceColumns.d.ts.map +1 -1
- package/dist/DboBuilder/schema/getViewReferenceColumns.js +4 -2
- package/dist/DboBuilder/schema/getViewReferenceColumns.js.map +1 -1
- package/dist/Prostgles.d.ts +6 -0
- package/dist/Prostgles.d.ts.map +1 -1
- package/dist/Prostgles.js +33 -5
- package/dist/Prostgles.js.map +1 -1
- package/dist/ProstglesTypes.d.ts +3 -0
- package/dist/ProstglesTypes.d.ts.map +1 -1
- package/dist/ProstglesTypes.js.map +1 -1
- package/dist/PubSubManager/PubSubManager.d.ts +1 -1
- package/dist/PubSubManager/getCreatePubSubManagerError.js +1 -1
- package/dist/PubSubManager/getCreatePubSubManagerError.js.map +1 -1
- package/dist/PubSubManager/init/getPubSubManagerInitQuery.js +4 -4
- package/dist/PubSubManager/init/getPubSubManagerInitQuery.js.map +1 -1
- package/dist/PublishParser/getTableRulesWithoutFileTable.d.ts.map +1 -1
- package/dist/PublishParser/getTableRulesWithoutFileTable.js +5 -1
- package/dist/PublishParser/getTableRulesWithoutFileTable.js.map +1 -1
- package/dist/TableConfig/TableConfigTypes.d.ts +1 -1
- package/dist/TableConfig/TableConfigTypes.d.ts.map +1 -1
- package/dist/TableConfig/TableConfigurator.js +3 -3
- package/dist/TableConfig/TableConfigurator.js.map +1 -1
- package/dist/TableConfig/getTableConfigSchemaQueries.d.ts.map +1 -1
- package/dist/TableConfig/getTableConfigSchemaQueries.js +3 -2
- package/dist/TableConfig/getTableConfigSchemaQueries.js.map +1 -1
- package/dist/TableConfig/getTableTriggerQueries.d.ts +2 -2
- package/dist/TableConfig/getTableTriggerQueries.d.ts.map +1 -1
- package/dist/TableConfig/getTableTriggerQueries.js +57 -34
- package/dist/TableConfig/getTableTriggerQueries.js.map +1 -1
- package/dist/TableConfig/initTableConfig.d.ts.map +1 -1
- package/dist/TableConfig/initTableConfig.js +5 -6
- package/dist/TableConfig/initTableConfig.js.map +1 -1
- package/dist/TableConfig/managedTriggerNames.d.ts +8 -0
- package/dist/TableConfig/managedTriggerNames.d.ts.map +1 -0
- package/dist/TableConfig/managedTriggerNames.js +17 -0
- package/dist/TableConfig/managedTriggerNames.js.map +1 -0
- package/dist/TableConfig/runSQLFile.js +1 -1
- package/dist/TableConfig/runSQLFile.js.map +1 -1
- package/dist/TableConfig/syncTableTriggers.d.ts +4 -0
- package/dist/TableConfig/syncTableTriggers.d.ts.map +1 -0
- package/dist/TableConfig/syncTableTriggers.js +32 -0
- package/dist/TableConfig/syncTableTriggers.js.map +1 -0
- package/dist/deferDbQueries.d.ts +4 -0
- package/dist/deferDbQueries.d.ts.map +1 -0
- package/dist/deferDbQueries.js +24 -0
- package/dist/deferDbQueries.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/initProstgles.d.ts +1 -1
- package/dist/initProstgles.d.ts.map +1 -1
- package/dist/initProstgles.js +24 -13
- package/dist/initProstgles.js.map +1 -1
- package/dist/updateConfiguration.d.ts.map +1 -1
- package/dist/updateConfiguration.js +1 -6
- package/dist/updateConfiguration.js.map +1 -1
- package/lib/Audit/AuditTable.ts +25 -0
- package/lib/Audit/AuditTypes.ts +35 -0
- package/lib/Audit/getAuditTableConfig.ts +76 -0
- package/lib/Audit/parseAuditConfig.ts +158 -0
- package/lib/DboBuilder/DboBuilder.ts +2 -2
- package/lib/DboBuilder/ViewHandler/getInfo.ts +5 -58
- package/lib/DboBuilder/ViewHandler/getRawInfo.ts +65 -0
- package/lib/DboBuilder/getColumns.ts +105 -92
- package/lib/DboBuilder/runSql/QueryStreamer.ts +1 -0
- package/lib/DboBuilder/schema/getTablesForSchemaPostgresSQL.ts +2 -2
- package/lib/DboBuilder/schema/getViewReferenceColumns.ts +7 -8
- package/lib/Prostgles.ts +34 -5
- package/lib/ProstglesTypes.ts +3 -0
- package/lib/PubSubManager/getCreatePubSubManagerError.ts +1 -1
- package/lib/PubSubManager/init/getPubSubManagerInitQuery.ts +4 -4
- package/lib/PublishParser/getTableRulesWithoutFileTable.ts +6 -1
- package/lib/TableConfig/TableConfigTypes.ts +1 -1
- package/lib/TableConfig/TableConfigurator.ts +2 -2
- package/lib/TableConfig/getTableConfigSchemaQueries.ts +3 -2
- package/lib/TableConfig/getTableTriggerQueries.ts +85 -41
- package/lib/TableConfig/initTableConfig.ts +4 -7
- package/lib/TableConfig/managedTriggerNames.ts +16 -0
- package/lib/TableConfig/runSQLFile.ts +1 -1
- package/lib/TableConfig/syncTableTriggers.ts +42 -0
- package/lib/deferDbQueries.ts +27 -0
- package/lib/index.ts +3 -0
- package/lib/initProstgles.ts +24 -12
- package/lib/updateConfiguration.ts +1 -6
- package/package.json +1 -1
- package/dist/TableConfig/getTableTriggerQueries.spec.d.ts +0 -2
- package/dist/TableConfig/getTableTriggerQueries.spec.d.ts.map +0 -1
- package/dist/TableConfig/getTableTriggerQueries.spec.js +0 -56
- package/dist/TableConfig/getTableTriggerQueries.spec.js.map +0 -1
- package/lib/TableConfig/getTableTriggerQueries.spec.ts +0 -77
|
@@ -1,73 +1,117 @@
|
|
|
1
1
|
import { as } from "pg-promise";
|
|
2
|
-
import { md5 } from "prostgles-types/dist/md5";
|
|
3
2
|
import type { DB } from "../initProstgles";
|
|
3
|
+
import { asName } from "prostgles-types";
|
|
4
4
|
import type { TableConfig } from "./TableConfigTypes";
|
|
5
|
+
import {
|
|
6
|
+
AUDIT_HISTORY_PROTECTION_PREFIX,
|
|
7
|
+
getManagedTriggerName,
|
|
8
|
+
isManagedTriggerName,
|
|
9
|
+
} from "./managedTriggerNames";
|
|
5
10
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
/** Reconcile only triggers owned by tableConfig; preserve manually created triggers. */
|
|
11
|
+
/** Compare actual definitions so unchanged triggers do not cause schema refreshes. */
|
|
9
12
|
export const getTableTriggerQueries = async (
|
|
10
13
|
db: DB,
|
|
11
|
-
|
|
14
|
+
tableIdent: string,
|
|
12
15
|
tableConfig: TableConfig[string],
|
|
13
|
-
asName: (name: string) => string,
|
|
14
16
|
): Promise<string[]> => {
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
+
const existing = await db.any<{
|
|
18
|
+
name: string;
|
|
19
|
+
type: number;
|
|
20
|
+
enabled: string;
|
|
21
|
+
function_name: string;
|
|
22
|
+
query: string;
|
|
23
|
+
old_table: string | null;
|
|
24
|
+
new_table: string | null;
|
|
25
|
+
args: number;
|
|
26
|
+
unconditional: boolean;
|
|
27
|
+
visible_function: boolean;
|
|
28
|
+
}>(
|
|
17
29
|
`
|
|
18
|
-
SELECT t.tgname AS name,
|
|
19
|
-
|
|
20
|
-
|
|
30
|
+
SELECT t.tgname AS name, t.tgtype AS type, t.tgenabled AS enabled,
|
|
31
|
+
p.proname AS function_name, p.prosrc AS query, t.tgoldtable AS old_table,
|
|
32
|
+
t.tgnewtable AS new_table, t.tgnargs AS args,
|
|
33
|
+
(t.tgqual IS NULL AND t.tgattr::text = '' AND t.tgconstraint = 0) AS unconditional,
|
|
34
|
+
p.oid = to_regprocedure(quote_ident(p.proname) || '()') AS visible_function
|
|
35
|
+
FROM pg_trigger t JOIN pg_proc p ON p.oid = t.tgfoid
|
|
36
|
+
WHERE t.tgrelid = to_regclass($1) AND NOT t.tgisinternal AND t.tgparentid = 0
|
|
21
37
|
`,
|
|
22
38
|
[tableIdent],
|
|
23
39
|
);
|
|
24
40
|
const queries: string[] = [];
|
|
25
41
|
const desired = new Set<string>();
|
|
26
|
-
const
|
|
42
|
+
const actionBits = { insert: 4, delete: 8, update: 16, truncate: 32 };
|
|
27
43
|
for (const [functionName, trigger] of Object.entries(
|
|
28
44
|
tableConfig.triggers ?? {},
|
|
29
45
|
)) {
|
|
30
46
|
const functionIdent = asName(functionName);
|
|
31
47
|
let functionAdded = false;
|
|
32
48
|
for (const action of trigger.actions) {
|
|
33
|
-
const name =
|
|
49
|
+
const name = getManagedTriggerName(functionName, action);
|
|
34
50
|
desired.add(name);
|
|
51
|
+
// Replace the unprefixed names used by earlier tableConfig versions.
|
|
52
|
+
const legacyName = `${functionName}_${action}`;
|
|
53
|
+
if (
|
|
54
|
+
legacyName !== name &&
|
|
55
|
+
existing.some(
|
|
56
|
+
(t) => t.name === legacyName && t.function_name === functionName,
|
|
57
|
+
)
|
|
58
|
+
) {
|
|
59
|
+
queries.push(`DROP TRIGGER ${asName(legacyName)} ON ${tableIdent};`);
|
|
60
|
+
}
|
|
35
61
|
const triggerIdent = asName(name);
|
|
36
|
-
const
|
|
37
|
-
trigger.forEach === "
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
62
|
+
const hasTransitionTables =
|
|
63
|
+
trigger.forEach === "statement" &&
|
|
64
|
+
trigger.type === "after" &&
|
|
65
|
+
action !== "truncate";
|
|
66
|
+
const oldTable =
|
|
67
|
+
hasTransitionTables && action !== "insert" ? "old_table" : null;
|
|
68
|
+
const newTable =
|
|
69
|
+
hasTransitionTables && action !== "delete" ? "new_table" : null;
|
|
70
|
+
const transitionTables = hasTransitionTables
|
|
71
|
+
? [
|
|
72
|
+
"REFERENCING",
|
|
73
|
+
newTable ? "NEW TABLE AS new_table" : "",
|
|
74
|
+
oldTable ? "OLD TABLE AS old_table" : "",
|
|
75
|
+
].join(" ")
|
|
76
|
+
: "";
|
|
77
|
+
const type =
|
|
78
|
+
actionBits[action] |
|
|
79
|
+
(trigger.forEach === "row" ? 1 : 0) |
|
|
80
|
+
(trigger.type === "before"
|
|
81
|
+
? 2
|
|
82
|
+
: trigger.type === "instead of"
|
|
83
|
+
? 64
|
|
84
|
+
: 0);
|
|
49
85
|
const previous = existing.find((entry) => entry.name === name);
|
|
50
|
-
if (
|
|
86
|
+
if (
|
|
87
|
+
previous?.type === type &&
|
|
88
|
+
previous.enabled === "O" &&
|
|
89
|
+
previous.function_name === functionName &&
|
|
90
|
+
previous.query === trigger.query &&
|
|
91
|
+
previous.old_table === oldTable &&
|
|
92
|
+
previous.new_table === newTable &&
|
|
93
|
+
!previous.args &&
|
|
94
|
+
previous.unconditional &&
|
|
95
|
+
previous.visible_function
|
|
96
|
+
)
|
|
97
|
+
continue;
|
|
51
98
|
if (!functionAdded) {
|
|
52
|
-
queries.push(
|
|
53
|
-
RETURNS trigger LANGUAGE plpgsql AS ${as.text(trigger.query)}
|
|
99
|
+
queries.push(
|
|
100
|
+
`CREATE OR REPLACE FUNCTION ${functionIdent}() RETURNS trigger LANGUAGE plpgsql AS ${as.text(trigger.query)};`,
|
|
101
|
+
);
|
|
54
102
|
functionAdded = true;
|
|
55
103
|
}
|
|
56
104
|
queries.push(`DROP TRIGGER IF EXISTS ${triggerIdent} ON ${tableIdent};`);
|
|
57
|
-
queries.push(
|
|
58
|
-
|
|
59
|
-
`COMMENT ON TRIGGER ${triggerIdent} ON ${tableIdent} IS ${as.text(comment)};`,
|
|
60
|
-
);
|
|
105
|
+
queries.push(`CREATE TRIGGER ${triggerIdent} ${trigger.type} ${action} ON ${tableIdent}
|
|
106
|
+
${transitionTables} FOR EACH ${trigger.forEach} EXECUTE FUNCTION ${functionIdent}();`);
|
|
61
107
|
}
|
|
62
108
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
) {
|
|
69
|
-
queries.push(`DROP TRIGGER ${asName(trigger.name)} ON ${tableIdent};`);
|
|
70
|
-
}
|
|
109
|
+
for (const trigger of existing) {
|
|
110
|
+
const managed = isManagedTriggerName(trigger.name);
|
|
111
|
+
// History remains append-only even after audit is disabled or moved.
|
|
112
|
+
const protectsHistory = trigger.name.startsWith(AUDIT_HISTORY_PROTECTION_PREFIX);
|
|
113
|
+
if (managed && !protectsHistory && !desired.has(trigger.name)) {
|
|
114
|
+
queries.push(`DROP TRIGGER ${asName(trigger.name)} ON ${tableIdent};`);
|
|
71
115
|
}
|
|
72
116
|
}
|
|
73
117
|
return queries;
|
|
@@ -9,10 +9,10 @@ import { fetchTableConstraints } from "./fetchTableConstraints";
|
|
|
9
9
|
import { getConstraintDefinitionQueries } from "./getConstraintDefinitionQueries";
|
|
10
10
|
import { getFutureTableSchema } from "./getFutureTableSchema";
|
|
11
11
|
import { getTableColumnQueries } from "./getTableColumnQueries";
|
|
12
|
-
import { getTableTriggerQueries } from "./getTableTriggerQueries";
|
|
13
12
|
import { getIndexesQueries } from "./indexes/getIndexesQueries";
|
|
14
13
|
import { runMigrations } from "./runMigrations";
|
|
15
14
|
import type { TableConfigurator } from "./TableConfigurator";
|
|
15
|
+
import { syncTableTriggers } from "./syncTableTriggers";
|
|
16
16
|
|
|
17
17
|
export const initTableConfig = async function (this: TableConfigurator) {
|
|
18
18
|
this.initialising = true;
|
|
@@ -234,8 +234,6 @@ export const initTableConfig = async function (this: TableConfigurator) {
|
|
|
234
234
|
|
|
235
235
|
const indexQueries = await getIndexesQueries(this.db, tableName, tableConf);
|
|
236
236
|
queries.push(...indexQueries);
|
|
237
|
-
|
|
238
|
-
queries.push(...(await getTableTriggerQueries(this.db, tableName, tableConf, asName)));
|
|
239
237
|
}
|
|
240
238
|
|
|
241
239
|
if (queries.length) {
|
|
@@ -268,7 +266,7 @@ export const initTableConfig = async function (this: TableConfigurator) {
|
|
|
268
266
|
}
|
|
269
267
|
this.initialising = false;
|
|
270
268
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
271
|
-
if (changedSchema && !failedQueries.length) {
|
|
269
|
+
if (changedSchema && !failedQueries.length && !this.prostgles.opts.audit) {
|
|
272
270
|
if (!this.prevInitQueryHistory) {
|
|
273
271
|
this.prevInitQueryHistory = queryHistory;
|
|
274
272
|
} else if (this.prevInitQueryHistory.join() !== queryHistory.join()) {
|
|
@@ -284,7 +282,6 @@ export const initTableConfig = async function (this: TableConfigurator) {
|
|
|
284
282
|
}
|
|
285
283
|
|
|
286
284
|
await this.prostgles.rebuildDBO();
|
|
287
|
-
await this.
|
|
288
|
-
|
|
289
|
-
await this.prostgles.rebuildDBO();
|
|
285
|
+
await syncTableTriggers(this.prostgles);
|
|
286
|
+
if (this.prostgles.opts.audit) await this.prostgles.rebuildDBO();
|
|
290
287
|
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { md5 } from "prostgles-types/dist/md5";
|
|
2
|
+
|
|
3
|
+
// Persisted database names: creation and cleanup must use the same prefixes.
|
|
4
|
+
// Keep generated names within PostgreSQL's 63-byte identifier limit.
|
|
5
|
+
export const TABLE_CONFIG_TRIGGER_PREFIX = "prostgles_table_config_";
|
|
6
|
+
export const AUDIT_TRIGGER_PREFIX = "prostgles_audit_";
|
|
7
|
+
// History protection survives removing or changing the audit configuration.
|
|
8
|
+
export const AUDIT_HISTORY_PROTECTION_PREFIX = AUDIT_TRIGGER_PREFIX + "guard_";
|
|
9
|
+
export const AUDIT_TRUNCATE_PROTECTION_PREFIX = AUDIT_TRIGGER_PREFIX + "block_";
|
|
10
|
+
export const AUDIT_WRITER_PREFIX = AUDIT_TRIGGER_PREFIX + "write_";
|
|
11
|
+
|
|
12
|
+
export const isManagedTriggerName = (name: string) =>
|
|
13
|
+
name.startsWith(TABLE_CONFIG_TRIGGER_PREFIX) || name.startsWith(AUDIT_TRIGGER_PREFIX);
|
|
14
|
+
|
|
15
|
+
export const getManagedTriggerName = (functionName: string, action: string) =>
|
|
16
|
+
`${functionName.startsWith(AUDIT_TRIGGER_PREFIX) ? functionName : TABLE_CONFIG_TRIGGER_PREFIX + md5(functionName)}_${action}`;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { parseAuditConfig } from "../Audit/parseAuditConfig";
|
|
2
|
+
import type { Prostgles } from "../Prostgles";
|
|
3
|
+
import { EXCLUDE_QUERY_FROM_SCHEMA_WATCH_ID } from "../PubSubManager/PubSubManagerUtils";
|
|
4
|
+
import { getTableTriggerQueries } from "./getTableTriggerQueries";
|
|
5
|
+
|
|
6
|
+
/** Applies application and generated audit triggers together, after all tables exist. */
|
|
7
|
+
export async function syncTableTriggers(prgl: Prostgles) {
|
|
8
|
+
const previousAudit = prgl.parsedAuditConfig;
|
|
9
|
+
const nextAudit =
|
|
10
|
+
prgl.opts.audit || previousAudit ? await parseAuditConfig(prgl) : undefined;
|
|
11
|
+
prgl.parsedAuditConfig = nextAudit;
|
|
12
|
+
try {
|
|
13
|
+
const config = prgl.mergedTableConfig.tableConfig ?? {};
|
|
14
|
+
const queries: string[] = [];
|
|
15
|
+
for (const name of Object.keys(config)) {
|
|
16
|
+
if (!prgl.dboBuilder.tables.some((t) => t.name === name)) {
|
|
17
|
+
throw new Error(
|
|
18
|
+
`Table config name must exactly match a schema table name: ${name}`,
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
for (const table of prgl.dboBuilder.tables) {
|
|
23
|
+
queries.push(
|
|
24
|
+
...(await getTableTriggerQueries(
|
|
25
|
+
prgl.dbForSchema!,
|
|
26
|
+
table.escaped_identifier,
|
|
27
|
+
{ triggers: config[table.name]?.triggers },
|
|
28
|
+
)),
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
if (queries.length) {
|
|
32
|
+
await prgl.dbForSchema!.tx((tx) =>
|
|
33
|
+
tx.none(
|
|
34
|
+
`/* ${EXCLUDE_QUERY_FROM_SCHEMA_WATCH_ID} */\n${queries.join("\n")}`,
|
|
35
|
+
),
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
} catch (error) {
|
|
39
|
+
prgl.parsedAuditConfig = previousAudit;
|
|
40
|
+
throw error;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { DB } from "./initProstgles";
|
|
2
|
+
|
|
3
|
+
/** Keep the original connection available for schema SQL; defer application calls. */
|
|
4
|
+
export const deferDbQueries = (
|
|
5
|
+
db: DB,
|
|
6
|
+
getSchemaReady: () => Promise<void>,
|
|
7
|
+
): DB =>
|
|
8
|
+
new Proxy(db, {
|
|
9
|
+
get(target, key, receiver) {
|
|
10
|
+
const value: unknown = Reflect.get(target, key, receiver);
|
|
11
|
+
// Connection metadata and lifecycle methods remain unchanged.
|
|
12
|
+
if (
|
|
13
|
+
!Object.hasOwn(target, key) ||
|
|
14
|
+
typeof value !== "function" ||
|
|
15
|
+
String(key).startsWith("$")
|
|
16
|
+
)
|
|
17
|
+
return value;
|
|
18
|
+
return async (...args: unknown[]) => {
|
|
19
|
+
let ready: Promise<void>;
|
|
20
|
+
do {
|
|
21
|
+
ready = getSchemaReady();
|
|
22
|
+
await ready;
|
|
23
|
+
} while (ready !== getSchemaReady());
|
|
24
|
+
return Reflect.apply(value, target, args) as unknown;
|
|
25
|
+
};
|
|
26
|
+
},
|
|
27
|
+
});
|
package/lib/index.ts
CHANGED
|
@@ -40,8 +40,11 @@ export type {
|
|
|
40
40
|
} from "./initProstgles";
|
|
41
41
|
export type { ProstglesInitOptions } from "./ProstglesTypes";
|
|
42
42
|
export type * from "./TableConfig/TableConfigTypes";
|
|
43
|
+
export type * from "./Audit/AuditTypes";
|
|
43
44
|
export type * from "./TableHooks/TableHooks";
|
|
44
45
|
export * from "./Auth/utils/upsertNamedExpressMiddleware";
|
|
45
46
|
export type { RequestWithUser } from "./Auth/middleware/userContextMiddleware";
|
|
46
47
|
export default prostgles;
|
|
47
48
|
export type { FileTableRow } from "./StorageClient/getFileTableConfig";
|
|
49
|
+
|
|
50
|
+
export { AUDIT_TABLE_COLUMN_DEFINITIONS, AUDIT_TABLE_COLUMNS, type AuditTableRow } from "./Audit/AuditTable";
|
package/lib/initProstgles.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { syncTableTriggers } from "./TableConfig/syncTableTriggers";
|
|
2
|
+
import { deferDbQueries } from "./deferDbQueries";
|
|
1
3
|
import type pgPromise from "pg-promise";
|
|
2
4
|
import type pg from "pg-promise/typescript/pg-subset";
|
|
3
5
|
import type { MaybePromise, SQLHandler, TableSchema } from "prostgles-types";
|
|
@@ -42,6 +44,7 @@ export type UpdatableOptions<
|
|
|
42
44
|
| "restApi"
|
|
43
45
|
| "tableHooks"
|
|
44
46
|
| "tableConfig"
|
|
47
|
+
| "audit"
|
|
45
48
|
| "schemaFilter"
|
|
46
49
|
| "auth"
|
|
47
50
|
| "publish"
|
|
@@ -209,7 +212,8 @@ export const initProstgles = async function (
|
|
|
209
212
|
)
|
|
210
213
|
.catch(() => {});
|
|
211
214
|
|
|
212
|
-
this.
|
|
215
|
+
this.dbForSchema = db;
|
|
216
|
+
this.db = deferDbQueries(db, () => this.schemaReady);
|
|
213
217
|
this.pgp = pgp;
|
|
214
218
|
this.isSuperUser = await getIsSuperUser(db);
|
|
215
219
|
}
|
|
@@ -221,17 +225,24 @@ export const initProstgles = async function (
|
|
|
221
225
|
try {
|
|
222
226
|
await this.cleanupContext();
|
|
223
227
|
|
|
224
|
-
|
|
225
|
-
await
|
|
226
|
-
|
|
227
|
-
|
|
228
|
+
await this.tableConfigurator?.destroy();
|
|
229
|
+
await this.runSchemaQueries(async () => {
|
|
230
|
+
/* 2. Execute any SQL file if provided */
|
|
231
|
+
await runSQLFile(this);
|
|
232
|
+
this.preparingTableConfig = true;
|
|
233
|
+
try {
|
|
234
|
+
await this.rebuildDBO();
|
|
235
|
+
await this.initTableConfig(reason);
|
|
236
|
+
} finally {
|
|
237
|
+
this.preparingTableConfig = false;
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
await this.tableConfigurator?.setTableOnMounts();
|
|
241
|
+
// onMount may create additional targets; validate joins against the completed schema.
|
|
242
|
+
await this.runSchemaQueries(async () => {
|
|
228
243
|
await this.rebuildDBO();
|
|
229
|
-
await this
|
|
230
|
-
}
|
|
231
|
-
this.preparingTableConfig = false;
|
|
232
|
-
}
|
|
233
|
-
// Validate all configured joins against the completed schema before publishing.
|
|
234
|
-
await this.rebuildDBO();
|
|
244
|
+
await syncTableTriggers(this);
|
|
245
|
+
});
|
|
235
246
|
await this.createContext(reason);
|
|
236
247
|
this.initRestApi();
|
|
237
248
|
|
|
@@ -267,7 +278,7 @@ export const initProstgles = async function (
|
|
|
267
278
|
{
|
|
268
279
|
sql: this.dboBuilder.sql,
|
|
269
280
|
dbo: this.dbo!,
|
|
270
|
-
db
|
|
281
|
+
db,
|
|
271
282
|
tables: this.dboBuilder.tables,
|
|
272
283
|
reason,
|
|
273
284
|
context: this.context,
|
|
@@ -324,6 +335,7 @@ export const initProstgles = async function (
|
|
|
324
335
|
await this.tableConfigurator?.destroy();
|
|
325
336
|
this.dbo = undefined;
|
|
326
337
|
this.db = undefined;
|
|
338
|
+
this.dbForSchema = undefined;
|
|
327
339
|
await db.$pool.end();
|
|
328
340
|
await this.adminClient?.end().catch(() => {});
|
|
329
341
|
this.adminClient = undefined;
|
|
@@ -20,6 +20,7 @@ export const updateConfiguration = async (
|
|
|
20
20
|
})
|
|
21
21
|
.filter(isDefined);
|
|
22
22
|
if (!optionsThatChanged.length) {
|
|
23
|
+
await prgl.schemaReady;
|
|
23
24
|
console.warn("No options changed");
|
|
24
25
|
return;
|
|
25
26
|
}
|
|
@@ -27,12 +28,6 @@ export const updateConfiguration = async (
|
|
|
27
28
|
if (includes(optionsThatChanged, "restApi")) {
|
|
28
29
|
prgl.initRestApi();
|
|
29
30
|
}
|
|
30
|
-
if (includes(optionsThatChanged, "tableConfig")) {
|
|
31
|
-
await prgl.initTableConfig({
|
|
32
|
-
type: "prgl.update",
|
|
33
|
-
newOpts,
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
31
|
if (includes(optionsThatChanged, "schema")) {
|
|
37
32
|
await prgl.refreshDBO();
|
|
38
33
|
}
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"getTableTriggerQueries.spec.d.ts","sourceRoot":"","sources":["../../lib/TableConfig/getTableTriggerQueries.spec.ts"],"names":[],"mappings":""}
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
const strict_1 = __importDefault(require("node:assert/strict"));
|
|
7
|
-
const node_test_1 = __importDefault(require("node:test"));
|
|
8
|
-
const prostgles_types_1 = require("prostgles-types");
|
|
9
|
-
const getTableTriggerQueries_1 = require("./getTableTriggerQueries");
|
|
10
|
-
void (0, node_test_1.default)("trigger configuration quotes names and bodies and preserves unmanaged triggers", async () => {
|
|
11
|
-
const db = {
|
|
12
|
-
any: () => Promise.resolve([
|
|
13
|
-
{ name: "manual", comment: null },
|
|
14
|
-
{ name: "old_insert", comment: "prostgles.tableConfig:old" },
|
|
15
|
-
]),
|
|
16
|
-
};
|
|
17
|
-
const queries = await (0, getTableTriggerQueries_1.getTableTriggerQueries)(db, "Odd ' table", {
|
|
18
|
-
triggers: {
|
|
19
|
-
audit: {
|
|
20
|
-
type: "after",
|
|
21
|
-
actions: ["insert"],
|
|
22
|
-
forEach: "row",
|
|
23
|
-
query: "BEGIN RAISE NOTICE '$$'; RETURN NULL; END;",
|
|
24
|
-
},
|
|
25
|
-
},
|
|
26
|
-
}, prostgles_types_1.asName);
|
|
27
|
-
strict_1.default.ok(queries.some((query) => query.includes('ON "Odd \' table"')));
|
|
28
|
-
strict_1.default.ok(queries.some((query) => query.includes("AS 'BEGIN RAISE NOTICE ''$$''; RETURN NULL; END;';")));
|
|
29
|
-
strict_1.default.ok(queries.some((query) => query.includes('DROP TRIGGER "old_insert"')));
|
|
30
|
-
strict_1.default.ok(!queries.some((query) => query.includes('DROP TRIGGER "manual"')));
|
|
31
|
-
});
|
|
32
|
-
void (0, node_test_1.default)("unchanged triggers do not produce DDL; changed bodies replace them", async () => {
|
|
33
|
-
const trigger = {
|
|
34
|
-
type: "after",
|
|
35
|
-
actions: ["update"],
|
|
36
|
-
forEach: "row",
|
|
37
|
-
query: "BEGIN RETURN NULL; END;",
|
|
38
|
-
};
|
|
39
|
-
const config = {
|
|
40
|
-
triggers: { audit: { ...trigger, actions: [...trigger.actions] } },
|
|
41
|
-
};
|
|
42
|
-
const first = await (0, getTableTriggerQueries_1.getTableTriggerQueries)({ any: () => Promise.resolve([]) }, "records", config, prostgles_types_1.asName);
|
|
43
|
-
const commentQuery = first.find((query) => query.startsWith("COMMENT ON"));
|
|
44
|
-
const comment = commentQuery.split(" IS '")[1].slice(0, -2);
|
|
45
|
-
const db = {
|
|
46
|
-
any: () => Promise.resolve([{ name: "audit_update", comment }]),
|
|
47
|
-
};
|
|
48
|
-
strict_1.default.deepEqual(await (0, getTableTriggerQueries_1.getTableTriggerQueries)(db, "records", config, prostgles_types_1.asName), []);
|
|
49
|
-
config.triggers.audit.query =
|
|
50
|
-
"BEGIN RAISE NOTICE 'changed'; RETURN NULL; END;";
|
|
51
|
-
strict_1.default.ok((await (0, getTableTriggerQueries_1.getTableTriggerQueries)(db, "records", config, prostgles_types_1.asName)).some((query) => query.startsWith("CREATE OR REPLACE FUNCTION")));
|
|
52
|
-
strict_1.default.deepEqual(await (0, getTableTriggerQueries_1.getTableTriggerQueries)(db, "records", {}, prostgles_types_1.asName), [
|
|
53
|
-
'DROP TRIGGER "audit_update" ON "records";',
|
|
54
|
-
]);
|
|
55
|
-
});
|
|
56
|
-
//# sourceMappingURL=getTableTriggerQueries.spec.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"getTableTriggerQueries.spec.js","sourceRoot":"","sources":["../../lib/TableConfig/getTableTriggerQueries.spec.ts"],"names":[],"mappings":";;;;;AAAA,gEAAwC;AACxC,0DAA6B;AAC7B,qDAAyC;AAEzC,qEAAkE;AAElE,KAAK,IAAA,mBAAI,EAAC,gFAAgF,EAAE,KAAK,IAAI,EAAE;IACrG,MAAM,EAAE,GAAG;QACT,GAAG,EAAE,GAAG,EAAE,CACR,OAAO,CAAC,OAAO,CAAC;YACd,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE;YACjC,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,2BAA2B,EAAE;SAC7D,CAAC;KACY,CAAC;IACnB,MAAM,OAAO,GAAG,MAAM,IAAA,+CAAsB,EAC1C,EAAE,EACF,aAAa,EACb;QACE,QAAQ,EAAE;YACR,KAAK,EAAE;gBACL,IAAI,EAAE,OAAO;gBACb,OAAO,EAAE,CAAC,QAAQ,CAAC;gBACnB,OAAO,EAAE,KAAK;gBACd,KAAK,EAAE,4CAA4C;aACpD;SACF;KACF,EACD,wBAAM,CACP,CAAC;IACF,gBAAM,CAAC,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC;IACxE,gBAAM,CAAC,EAAE,CACP,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CACrB,KAAK,CAAC,QAAQ,CAAC,oDAAoD,CAAC,CACrE,CACF,CAAC;IACF,gBAAM,CAAC,EAAE,CACP,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,2BAA2B,CAAC,CAAC,CACrE,CAAC;IACF,gBAAM,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,uBAAuB,CAAC,CAAC,CAAC,CAAC;AAC/E,CAAC,CAAC,CAAC;AAEH,KAAK,IAAA,mBAAI,EAAC,oEAAoE,EAAE,KAAK,IAAI,EAAE;IACzF,MAAM,OAAO,GAAG;QACd,IAAI,EAAE,OAAO;QACb,OAAO,EAAE,CAAC,QAAQ,CAAC;QACnB,OAAO,EAAE,KAAK;QACd,KAAK,EAAE,yBAAmC;KAClC,CAAC;IACX,MAAM,MAAM,GAAG;QACb,QAAQ,EAAE,EAAE,KAAK,EAAE,EAAE,GAAG,OAAO,EAAE,OAAO,EAAE,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,EAAE,EAAE;KACnE,CAAC;IACF,MAAM,KAAK,GAAG,MAAM,IAAA,+CAAsB,EACxC,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,EAAmB,EACnD,SAAS,EACT,MAAM,EACN,wBAAM,CACP,CAAC;IACF,MAAM,YAAY,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,UAAU,CAAC,YAAY,CAAC,CAAE,CAAC;IAC5E,MAAM,OAAO,GAAG,YAAY,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAE,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAC7D,MAAM,EAAE,GAAG;QACT,GAAG,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,CAAC,CAAC;KAC/C,CAAC;IACnB,gBAAM,CAAC,SAAS,CACd,MAAM,IAAA,+CAAsB,EAAC,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,wBAAM,CAAC,EAC3D,EAAE,CACH,CAAC;IACF,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK;QACzB,iDAAiD,CAAC;IACpD,gBAAM,CAAC,EAAE,CACP,CAAC,MAAM,IAAA,+CAAsB,EAAC,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,wBAAM,CAAC,CAAC,CAAC,IAAI,CAChE,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,UAAU,CAAC,4BAA4B,CAAC,CAC1D,CACF,CAAC;IACF,gBAAM,CAAC,SAAS,CAAC,MAAM,IAAA,+CAAsB,EAAC,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,wBAAM,CAAC,EAAE;QACxE,2CAA2C;KAC5C,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import test from "node:test";
|
|
3
|
-
import { asName } from "prostgles-types";
|
|
4
|
-
import type { DB } from "../initProstgles";
|
|
5
|
-
import { getTableTriggerQueries } from "./getTableTriggerQueries";
|
|
6
|
-
|
|
7
|
-
void test("trigger configuration quotes names and bodies and preserves unmanaged triggers", async () => {
|
|
8
|
-
const db = {
|
|
9
|
-
any: () =>
|
|
10
|
-
Promise.resolve([
|
|
11
|
-
{ name: "manual", comment: null },
|
|
12
|
-
{ name: "old_insert", comment: "prostgles.tableConfig:old" },
|
|
13
|
-
]),
|
|
14
|
-
} as unknown as DB;
|
|
15
|
-
const queries = await getTableTriggerQueries(
|
|
16
|
-
db,
|
|
17
|
-
"Odd ' table",
|
|
18
|
-
{
|
|
19
|
-
triggers: {
|
|
20
|
-
audit: {
|
|
21
|
-
type: "after",
|
|
22
|
-
actions: ["insert"],
|
|
23
|
-
forEach: "row",
|
|
24
|
-
query: "BEGIN RAISE NOTICE '$$'; RETURN NULL; END;",
|
|
25
|
-
},
|
|
26
|
-
},
|
|
27
|
-
},
|
|
28
|
-
asName,
|
|
29
|
-
);
|
|
30
|
-
assert.ok(queries.some((query) => query.includes('ON "Odd \' table"')));
|
|
31
|
-
assert.ok(
|
|
32
|
-
queries.some((query) =>
|
|
33
|
-
query.includes("AS 'BEGIN RAISE NOTICE ''$$''; RETURN NULL; END;';"),
|
|
34
|
-
),
|
|
35
|
-
);
|
|
36
|
-
assert.ok(
|
|
37
|
-
queries.some((query) => query.includes('DROP TRIGGER "old_insert"')),
|
|
38
|
-
);
|
|
39
|
-
assert.ok(!queries.some((query) => query.includes('DROP TRIGGER "manual"')));
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
void test("unchanged triggers do not produce DDL; changed bodies replace them", async () => {
|
|
43
|
-
const trigger = {
|
|
44
|
-
type: "after",
|
|
45
|
-
actions: ["update"],
|
|
46
|
-
forEach: "row",
|
|
47
|
-
query: "BEGIN RETURN NULL; END;" as string,
|
|
48
|
-
} as const;
|
|
49
|
-
const config = {
|
|
50
|
-
triggers: { audit: { ...trigger, actions: [...trigger.actions] } },
|
|
51
|
-
};
|
|
52
|
-
const first = await getTableTriggerQueries(
|
|
53
|
-
{ any: () => Promise.resolve([]) } as unknown as DB,
|
|
54
|
-
"records",
|
|
55
|
-
config,
|
|
56
|
-
asName,
|
|
57
|
-
);
|
|
58
|
-
const commentQuery = first.find((query) => query.startsWith("COMMENT ON"))!;
|
|
59
|
-
const comment = commentQuery.split(" IS '")[1]!.slice(0, -2);
|
|
60
|
-
const db = {
|
|
61
|
-
any: () => Promise.resolve([{ name: "audit_update", comment }]),
|
|
62
|
-
} as unknown as DB;
|
|
63
|
-
assert.deepEqual(
|
|
64
|
-
await getTableTriggerQueries(db, "records", config, asName),
|
|
65
|
-
[],
|
|
66
|
-
);
|
|
67
|
-
config.triggers.audit.query =
|
|
68
|
-
"BEGIN RAISE NOTICE 'changed'; RETURN NULL; END;";
|
|
69
|
-
assert.ok(
|
|
70
|
-
(await getTableTriggerQueries(db, "records", config, asName)).some(
|
|
71
|
-
(query) => query.startsWith("CREATE OR REPLACE FUNCTION"),
|
|
72
|
-
),
|
|
73
|
-
);
|
|
74
|
-
assert.deepEqual(await getTableTriggerQueries(db, "records", {}, asName), [
|
|
75
|
-
'DROP TRIGGER "audit_update" ON "records";',
|
|
76
|
-
]);
|
|
77
|
-
});
|