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.
Files changed (130) hide show
  1. package/README.md +29 -0
  2. package/dist/Audit/AuditTable.d.ts +18 -0
  3. package/dist/Audit/AuditTable.d.ts.map +1 -0
  4. package/dist/Audit/AuditTable.js +18 -0
  5. package/dist/Audit/AuditTable.js.map +1 -0
  6. package/dist/Audit/AuditTypes.d.ts +25 -0
  7. package/dist/Audit/AuditTypes.d.ts.map +1 -0
  8. package/dist/Audit/AuditTypes.js +3 -0
  9. package/dist/Audit/AuditTypes.js.map +1 -0
  10. package/dist/Audit/getAuditTableConfig.d.ts +13 -0
  11. package/dist/Audit/getAuditTableConfig.d.ts.map +1 -0
  12. package/dist/Audit/getAuditTableConfig.js +54 -0
  13. package/dist/Audit/getAuditTableConfig.js.map +1 -0
  14. package/dist/Audit/parseAuditConfig.d.ts +5 -0
  15. package/dist/Audit/parseAuditConfig.d.ts.map +1 -0
  16. package/dist/Audit/parseAuditConfig.js +123 -0
  17. package/dist/Audit/parseAuditConfig.js.map +1 -0
  18. package/dist/DboBuilder/DboBuilder.js +2 -2
  19. package/dist/DboBuilder/DboBuilder.js.map +1 -1
  20. package/dist/DboBuilder/ViewHandler/ViewHandler.d.ts +1 -1
  21. package/dist/DboBuilder/ViewHandler/getInfo.d.ts.map +1 -1
  22. package/dist/DboBuilder/ViewHandler/getInfo.js +5 -46
  23. package/dist/DboBuilder/ViewHandler/getInfo.js.map +1 -1
  24. package/dist/DboBuilder/ViewHandler/getRawInfo.d.ts +7 -0
  25. package/dist/DboBuilder/ViewHandler/getRawInfo.d.ts.map +1 -0
  26. package/dist/DboBuilder/ViewHandler/getRawInfo.js +51 -0
  27. package/dist/DboBuilder/ViewHandler/getRawInfo.js.map +1 -0
  28. package/dist/DboBuilder/getColumns.d.ts +5 -0
  29. package/dist/DboBuilder/getColumns.d.ts.map +1 -1
  30. package/dist/DboBuilder/getColumns.js +80 -74
  31. package/dist/DboBuilder/getColumns.js.map +1 -1
  32. package/dist/DboBuilder/runSql/QueryStreamer.d.ts.map +1 -1
  33. package/dist/DboBuilder/runSql/QueryStreamer.js +1 -0
  34. package/dist/DboBuilder/runSql/QueryStreamer.js.map +1 -1
  35. package/dist/DboBuilder/schema/getTablesForSchemaPostgresSQL.d.ts +3 -3
  36. package/dist/DboBuilder/schema/getTablesForSchemaPostgresSQL.d.ts.map +1 -1
  37. package/dist/DboBuilder/schema/getTablesForSchemaPostgresSQL.js +2 -2
  38. package/dist/DboBuilder/schema/getTablesForSchemaPostgresSQL.js.map +1 -1
  39. package/dist/DboBuilder/schema/getViewReferenceColumns.d.ts +2 -2
  40. package/dist/DboBuilder/schema/getViewReferenceColumns.d.ts.map +1 -1
  41. package/dist/DboBuilder/schema/getViewReferenceColumns.js +4 -2
  42. package/dist/DboBuilder/schema/getViewReferenceColumns.js.map +1 -1
  43. package/dist/Prostgles.d.ts +6 -0
  44. package/dist/Prostgles.d.ts.map +1 -1
  45. package/dist/Prostgles.js +33 -5
  46. package/dist/Prostgles.js.map +1 -1
  47. package/dist/ProstglesTypes.d.ts +3 -0
  48. package/dist/ProstglesTypes.d.ts.map +1 -1
  49. package/dist/ProstglesTypes.js.map +1 -1
  50. package/dist/PubSubManager/PubSubManager.d.ts +1 -1
  51. package/dist/PubSubManager/getCreatePubSubManagerError.js +1 -1
  52. package/dist/PubSubManager/getCreatePubSubManagerError.js.map +1 -1
  53. package/dist/PubSubManager/init/getPubSubManagerInitQuery.js +4 -4
  54. package/dist/PubSubManager/init/getPubSubManagerInitQuery.js.map +1 -1
  55. package/dist/PublishParser/getTableRulesWithoutFileTable.d.ts.map +1 -1
  56. package/dist/PublishParser/getTableRulesWithoutFileTable.js +5 -1
  57. package/dist/PublishParser/getTableRulesWithoutFileTable.js.map +1 -1
  58. package/dist/TableConfig/TableConfigTypes.d.ts +1 -1
  59. package/dist/TableConfig/TableConfigTypes.d.ts.map +1 -1
  60. package/dist/TableConfig/TableConfigurator.js +3 -3
  61. package/dist/TableConfig/TableConfigurator.js.map +1 -1
  62. package/dist/TableConfig/getTableConfigSchemaQueries.d.ts.map +1 -1
  63. package/dist/TableConfig/getTableConfigSchemaQueries.js +3 -2
  64. package/dist/TableConfig/getTableConfigSchemaQueries.js.map +1 -1
  65. package/dist/TableConfig/getTableTriggerQueries.d.ts +2 -2
  66. package/dist/TableConfig/getTableTriggerQueries.d.ts.map +1 -1
  67. package/dist/TableConfig/getTableTriggerQueries.js +57 -34
  68. package/dist/TableConfig/getTableTriggerQueries.js.map +1 -1
  69. package/dist/TableConfig/initTableConfig.d.ts.map +1 -1
  70. package/dist/TableConfig/initTableConfig.js +5 -6
  71. package/dist/TableConfig/initTableConfig.js.map +1 -1
  72. package/dist/TableConfig/managedTriggerNames.d.ts +8 -0
  73. package/dist/TableConfig/managedTriggerNames.d.ts.map +1 -0
  74. package/dist/TableConfig/managedTriggerNames.js +17 -0
  75. package/dist/TableConfig/managedTriggerNames.js.map +1 -0
  76. package/dist/TableConfig/runSQLFile.js +1 -1
  77. package/dist/TableConfig/runSQLFile.js.map +1 -1
  78. package/dist/TableConfig/syncTableTriggers.d.ts +4 -0
  79. package/dist/TableConfig/syncTableTriggers.d.ts.map +1 -0
  80. package/dist/TableConfig/syncTableTriggers.js +32 -0
  81. package/dist/TableConfig/syncTableTriggers.js.map +1 -0
  82. package/dist/deferDbQueries.d.ts +4 -0
  83. package/dist/deferDbQueries.d.ts.map +1 -0
  84. package/dist/deferDbQueries.js +24 -0
  85. package/dist/deferDbQueries.js.map +1 -0
  86. package/dist/index.d.ts +2 -0
  87. package/dist/index.d.ts.map +1 -1
  88. package/dist/index.js +4 -1
  89. package/dist/index.js.map +1 -1
  90. package/dist/initProstgles.d.ts +1 -1
  91. package/dist/initProstgles.d.ts.map +1 -1
  92. package/dist/initProstgles.js +24 -13
  93. package/dist/initProstgles.js.map +1 -1
  94. package/dist/updateConfiguration.d.ts.map +1 -1
  95. package/dist/updateConfiguration.js +1 -6
  96. package/dist/updateConfiguration.js.map +1 -1
  97. package/lib/Audit/AuditTable.ts +25 -0
  98. package/lib/Audit/AuditTypes.ts +35 -0
  99. package/lib/Audit/getAuditTableConfig.ts +76 -0
  100. package/lib/Audit/parseAuditConfig.ts +158 -0
  101. package/lib/DboBuilder/DboBuilder.ts +2 -2
  102. package/lib/DboBuilder/ViewHandler/getInfo.ts +5 -58
  103. package/lib/DboBuilder/ViewHandler/getRawInfo.ts +65 -0
  104. package/lib/DboBuilder/getColumns.ts +105 -92
  105. package/lib/DboBuilder/runSql/QueryStreamer.ts +1 -0
  106. package/lib/DboBuilder/schema/getTablesForSchemaPostgresSQL.ts +2 -2
  107. package/lib/DboBuilder/schema/getViewReferenceColumns.ts +7 -8
  108. package/lib/Prostgles.ts +34 -5
  109. package/lib/ProstglesTypes.ts +3 -0
  110. package/lib/PubSubManager/getCreatePubSubManagerError.ts +1 -1
  111. package/lib/PubSubManager/init/getPubSubManagerInitQuery.ts +4 -4
  112. package/lib/PublishParser/getTableRulesWithoutFileTable.ts +6 -1
  113. package/lib/TableConfig/TableConfigTypes.ts +1 -1
  114. package/lib/TableConfig/TableConfigurator.ts +2 -2
  115. package/lib/TableConfig/getTableConfigSchemaQueries.ts +3 -2
  116. package/lib/TableConfig/getTableTriggerQueries.ts +85 -41
  117. package/lib/TableConfig/initTableConfig.ts +4 -7
  118. package/lib/TableConfig/managedTriggerNames.ts +16 -0
  119. package/lib/TableConfig/runSQLFile.ts +1 -1
  120. package/lib/TableConfig/syncTableTriggers.ts +42 -0
  121. package/lib/deferDbQueries.ts +27 -0
  122. package/lib/index.ts +3 -0
  123. package/lib/initProstgles.ts +24 -12
  124. package/lib/updateConfiguration.ts +1 -6
  125. package/package.json +1 -1
  126. package/dist/TableConfig/getTableTriggerQueries.spec.d.ts +0 -2
  127. package/dist/TableConfig/getTableTriggerQueries.spec.d.ts.map +0 -1
  128. package/dist/TableConfig/getTableTriggerQueries.spec.js +0 -56
  129. package/dist/TableConfig/getTableTriggerQueries.spec.js.map +0 -1
  130. 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
- const MANAGED_TRIGGER_PREFIX = "prostgles.tableConfig:";
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
- tableName: string,
14
+ tableIdent: string,
12
15
  tableConfig: TableConfig[string],
13
- asName: (name: string) => string,
14
16
  ): Promise<string[]> => {
15
- const tableIdent = asName(tableName);
16
- const existing = await db.any<{ name: string; comment: string | null }>(
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, obj_description(t.oid, 'pg_trigger') AS comment
19
- FROM pg_trigger t
20
- WHERE t.tgrelid = to_regclass($1) AND NOT t.tgisinternal
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 isDropped = tableConfig.dropIfExists || tableConfig.dropIfExistsCascade;
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 = `${functionName}_${action}`;
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 transitionTables =
37
- trigger.forEach === "row"
38
- ? ""
39
- : [
40
- "REFERENCING",
41
- action !== "delete" ? "NEW TABLE AS new_table" : "",
42
- action !== "insert" ? "OLD TABLE AS old_table" : "",
43
- ].join(" ");
44
- const definition = `CREATE TRIGGER ${triggerIdent}
45
- ${trigger.type} ${action} ON ${tableIdent}
46
- ${transitionTables} FOR EACH ${trigger.forEach}
47
- EXECUTE FUNCTION ${functionIdent}();`;
48
- const comment = MANAGED_TRIGGER_PREFIX + md5(definition + trigger.query);
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 (!isDropped && previous?.comment === comment) continue;
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(`CREATE OR REPLACE FUNCTION ${functionIdent}()
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(definition);
58
- queries.push(
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
- if (!isDropped) {
64
- for (const trigger of existing) {
65
- if (
66
- trigger.comment?.startsWith(MANAGED_TRIGGER_PREFIX) &&
67
- !desired.has(trigger.name)
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.setTableOnMounts();
288
- /** Needed in case some onMounts change schema */
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}`;
@@ -3,7 +3,7 @@ import { getFileText, type Prostgles } from "../Prostgles";
3
3
 
4
4
  export const runSQLFile = async (prostgles: Prostgles) => {
5
5
  const {
6
- db,
6
+ dbForSchema: db,
7
7
  opts: { sqlFilePath, onLog },
8
8
  } = prostgles;
9
9
  if (!sqlFilePath) return;
@@ -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";
@@ -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.db = db;
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
- /* 2. Execute any SQL file if provided */
225
- await runSQLFile(this);
226
- this.preparingTableConfig = true;
227
- try {
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.initTableConfig(reason);
230
- } finally {
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: this.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,6 +1,6 @@
1
1
  {
2
2
  "name": "prostgles-server",
3
- "version": "4.2.605",
3
+ "version": "4.2.607",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=getTableTriggerQueries.spec.d.ts.map
@@ -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":"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"}
@@ -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
- });