prostgles-server 4.2.551 → 4.2.554

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 (56) hide show
  1. package/dist/Auth/AuthTypes.d.ts +0 -5
  2. package/dist/Auth/AuthTypes.d.ts.map +1 -1
  3. package/dist/Auth/AuthTypes.js.map +1 -1
  4. package/dist/Auth/sendEmail.d.ts +3 -3
  5. package/dist/Auth/sendEmail.d.ts.map +1 -1
  6. package/dist/Auth/sendEmail.js +22 -25
  7. package/dist/Auth/sendEmail.js.map +1 -1
  8. package/dist/DboBuilder/TableHandler/TableHandler.d.ts +11 -3
  9. package/dist/DboBuilder/TableHandler/TableHandler.d.ts.map +1 -1
  10. package/dist/DboBuilder/TableHandler/TableHandler.js +49 -6
  11. package/dist/DboBuilder/TableHandler/TableHandler.js.map +1 -1
  12. package/dist/DboBuilder/TableHandler/delete.js +3 -3
  13. package/dist/DboBuilder/TableHandler/delete.js.map +1 -1
  14. package/dist/DboBuilder/TableHandler/{executeHooksCheckAndPostValidation.d.ts → executeAfterHooksCheckAndPostValidation.d.ts} +2 -2
  15. package/dist/DboBuilder/TableHandler/executeAfterHooksCheckAndPostValidation.d.ts.map +1 -0
  16. package/dist/DboBuilder/TableHandler/{executeHooksCheckAndPostValidation.js → executeAfterHooksCheckAndPostValidation.js} +19 -15
  17. package/dist/DboBuilder/TableHandler/executeAfterHooksCheckAndPostValidation.js.map +1 -0
  18. package/dist/DboBuilder/TableHandler/insert/insert.d.ts.map +1 -1
  19. package/dist/DboBuilder/TableHandler/insert/insert.js +11 -5
  20. package/dist/DboBuilder/TableHandler/insert/insert.js.map +1 -1
  21. package/dist/DboBuilder/TableHandler/isApplicableHook.d.ts +5 -0
  22. package/dist/DboBuilder/TableHandler/isApplicableHook.d.ts.map +1 -0
  23. package/dist/DboBuilder/TableHandler/isApplicableHook.js +17 -0
  24. package/dist/DboBuilder/TableHandler/isApplicableHook.js.map +1 -0
  25. package/dist/DboBuilder/TableHandler/runInsertUpdateQuery.js +2 -2
  26. package/dist/DboBuilder/TableHandler/runInsertUpdateQuery.js.map +1 -1
  27. package/dist/DboBuilder/TableHandler/update.d.ts.map +1 -1
  28. package/dist/DboBuilder/TableHandler/update.js +4 -1
  29. package/dist/DboBuilder/TableHandler/update.js.map +1 -1
  30. package/dist/FileManager/FileManager.js +1 -1
  31. package/dist/FileManager/FileManager.js.map +1 -1
  32. package/dist/PublishParser/publishTypesAndUtils.d.ts +28 -1
  33. package/dist/PublishParser/publishTypesAndUtils.d.ts.map +1 -1
  34. package/dist/PublishParser/publishTypesAndUtils.js.map +1 -1
  35. package/dist/TableConfig/TableConfig.d.ts +3 -2
  36. package/dist/TableConfig/TableConfig.d.ts.map +1 -1
  37. package/dist/TableConfig/TableConfig.js.map +1 -1
  38. package/dist/TableConfig/getCreateSchemaQueries.d.ts +2 -0
  39. package/dist/TableConfig/getCreateSchemaQueries.d.ts.map +1 -1
  40. package/dist/TableConfig/getTableConfigSchemaQueries.d.ts +2 -0
  41. package/dist/TableConfig/getTableConfigSchemaQueries.d.ts.map +1 -1
  42. package/lib/Auth/AuthTypes.ts +6 -11
  43. package/lib/Auth/sendEmail.ts +22 -29
  44. package/lib/DboBuilder/TableHandler/TableHandler.ts +59 -5
  45. package/lib/DboBuilder/TableHandler/delete.ts +3 -3
  46. package/lib/DboBuilder/TableHandler/{executeHooksCheckAndPostValidation.ts → executeAfterHooksCheckAndPostValidation.ts} +25 -16
  47. package/lib/DboBuilder/TableHandler/insert/insert.ts +21 -7
  48. package/lib/DboBuilder/TableHandler/isApplicableHook.ts +28 -0
  49. package/lib/DboBuilder/TableHandler/runInsertUpdateQuery.ts +2 -2
  50. package/lib/DboBuilder/TableHandler/update.ts +5 -1
  51. package/lib/FileManager/FileManager.ts +1 -1
  52. package/lib/PublishParser/publishTypesAndUtils.ts +31 -1
  53. package/lib/TableConfig/TableConfig.ts +3 -2
  54. package/package.json +11 -11
  55. package/dist/DboBuilder/TableHandler/executeHooksCheckAndPostValidation.d.ts.map +0 -1
  56. package/dist/DboBuilder/TableHandler/executeHooksCheckAndPostValidation.js.map +0 -1
@@ -98,11 +98,6 @@ export type SMTPConfig =
98
98
  region: string;
99
99
  accessKeyId: string;
100
100
  secretAccessKey: string;
101
- /**
102
- * Sending rate per second
103
- * Defaults to 1
104
- */
105
- sendingRate?: number;
106
101
  };
107
102
 
108
103
  export type Email = {
@@ -310,7 +305,7 @@ export type AuthConfig<S = void, SUser extends SessionUser = SessionUser> = {
310
305
  onUseOrSocketConnected?: (
311
306
  sid: string | undefined,
312
307
  client: LoginClientInfo,
313
- reqInfo: AuthClientRequest
308
+ reqInfo: AuthClientRequest,
314
309
  ) => Awaitable<void | { error: string; httpCode: 400 | 401 | 403 } | { session: BasicSession }>;
315
310
 
316
311
  /**
@@ -326,7 +321,7 @@ export type AuthConfig<S = void, SUser extends SessionUser = SessionUser> = {
326
321
  dbo: DBOFullyTyped<S>,
327
322
  db: DB,
328
323
  client: LoginClientInfo,
329
- reqInfo: AuthClientRequest
324
+ reqInfo: AuthClientRequest,
330
325
  ) => Awaitable<AuthResultOrError<SUser>>;
331
326
 
332
327
  /**
@@ -381,7 +376,7 @@ export const getMagicLinkUrl = ({
381
376
  }
382
377
  const { code, email } = data;
383
378
  const confirmationUrl = new URL(
384
- `${websiteUrl}${GET_AUTH_ROUTE(loginSignupConfig, "magicLinks")}`
379
+ `${websiteUrl}${GET_AUTH_ROUTE(loginSignupConfig, "magicLinks")}`,
385
380
  );
386
381
  confirmationUrl.searchParams.set("email", email);
387
382
  confirmationUrl.searchParams.set("code", code);
@@ -424,7 +419,7 @@ export type LoginSignupConfig<S, SUser extends SessionUser> = {
424
419
  onGetRequestOK?: (
425
420
  req: ExpressReq,
426
421
  res: ExpressRes,
427
- params: AuthRequestParams<S, SUser>
422
+ params: AuthRequestParams<S, SUser>,
428
423
  ) => Awaitable<void>;
429
424
 
430
425
  /**
@@ -439,7 +434,7 @@ export type LoginSignupConfig<S, SUser extends SessionUser> = {
439
434
  data: MagicLinkOrOTPData,
440
435
  dbo: DBOFullyTyped<S>,
441
436
  db: DB,
442
- client: LoginClientInfo
437
+ client: LoginClientInfo,
443
438
  ) => Awaitable<
444
439
  | {
445
440
  session: BasicSession | undefined;
@@ -470,7 +465,7 @@ export type LoginSignupConfig<S, SUser extends SessionUser> = {
470
465
  dbo: DBOFullyTyped<S>,
471
466
  db: DB,
472
467
  client: LoginClientInfo,
473
- getMagicLinkUrl: (data: MagicLinkOrOTPData, websiteUrl: string) => string
468
+ getMagicLinkUrl: (data: MagicLinkOrOTPData, websiteUrl: string) => string,
474
469
  ) => Awaitable<LoginResponse>;
475
470
 
476
471
  logout: (sid: string | undefined, dbo: DBOFullyTyped<S>, db: DB) => Awaitable<void>;
@@ -1,6 +1,6 @@
1
1
  import type { Email, SMTPConfig } from "./AuthTypes";
2
2
  import * as nodemailer from "nodemailer";
3
- import * as aws from "@aws-sdk/client-ses";
3
+ import { SESv2Client, SendEmailCommand } from "@aws-sdk/client-sesv2";
4
4
  import type SESTransport from "nodemailer/lib/ses-transport";
5
5
  import { checkDmarc } from "./utils/checkDmarc";
6
6
 
@@ -17,8 +17,8 @@ const transporterCache: Map<string, Transporter> = new Map();
17
17
  * Allows sending emails using nodemailer default config or AWS SES
18
18
  * https://www.nodemailer.com/transports/ses/
19
19
  */
20
- const sendEmail = (smptConfig: SMTPConfig, email: Email) => {
21
- const transporter = getOrSetTransporter(smptConfig);
20
+ const sendEmail = (smtpConfig: SMTPConfig, email: Email) => {
21
+ const transporter = getOrSetTransporter(smtpConfig);
22
22
  return send(transporter, email);
23
23
  };
24
24
 
@@ -26,11 +26,11 @@ const sendEmail = (smptConfig: SMTPConfig, email: Email) => {
26
26
  * Verifies DMARC and that the website has a valid DMARC records
27
27
  */
28
28
  const emailSenderCache: Map<string, boolean> = new Map();
29
- export const getEmailSender = async (smptConfig: SMTPConfig, websiteUrl: string) => {
29
+ export const getEmailSender = async (smtpConfig: SMTPConfig, websiteUrl: string) => {
30
30
  const result = {
31
- sendEmail: (email: Email) => sendEmail(smptConfig, email),
31
+ sendEmail: (email: Email) => sendEmail(smtpConfig, email),
32
32
  };
33
- const configStr = JSON.stringify({ smptConfig, websiteUrl });
33
+ const configStr = JSON.stringify({ smtpConfig, websiteUrl });
34
34
  if (emailSenderCache.has(configStr)) {
35
35
  return result;
36
36
  }
@@ -39,12 +39,12 @@ export const getEmailSender = async (smptConfig: SMTPConfig, websiteUrl: string)
39
39
  }
40
40
  await checkDmarc(websiteUrl);
41
41
 
42
- await verifySMTPConfig(smptConfig);
42
+ await verifySMTPConfig(smtpConfig);
43
43
 
44
44
  /**
45
45
  * Setup nodemailer transporters
46
46
  */
47
- getOrSetTransporter(smptConfig);
47
+ getOrSetTransporter(smtpConfig);
48
48
  emailSenderCache.set(configStr, true);
49
49
  return result;
50
50
  };
@@ -52,28 +52,20 @@ export const getEmailSender = async (smptConfig: SMTPConfig, websiteUrl: string)
52
52
  /**
53
53
  * Returns a transporter from cache or creates a new one
54
54
  */
55
- export const getOrSetTransporter = (smptConfig: SMTPConfig) => {
56
- const configStr = JSON.stringify(smptConfig);
57
- const transporter = transporterCache.get(configStr) ?? getTransporter(smptConfig);
55
+ export const getOrSetTransporter = (smtpConfig: SMTPConfig) => {
56
+ const configStr = JSON.stringify(smtpConfig);
57
+ const transporter = transporterCache.get(configStr) ?? getTransporter(smtpConfig);
58
58
  if (!transporterCache.has(configStr)) {
59
59
  transporterCache.set(configStr, transporter);
60
60
  }
61
61
  return transporter;
62
62
  };
63
63
 
64
- const getTransporter = (smptConfig: SMTPConfig) => {
64
+ const getTransporter = (smtpConfig: SMTPConfig) => {
65
65
  let transporter: Transporter | undefined;
66
- if (smptConfig.type === "aws-ses") {
67
- const {
68
- region,
69
- accessKeyId,
70
- secretAccessKey,
71
- /**
72
- * max 1 messages/second
73
- */
74
- sendingRate = 1,
75
- } = smptConfig;
76
- const ses = new aws.SES({
66
+ if (smtpConfig.type === "aws-ses") {
67
+ const { region, accessKeyId, secretAccessKey } = smtpConfig;
68
+ const sesClient = new SESv2Client({
77
69
  apiVersion: "2010-12-01",
78
70
  region,
79
71
  credentials: {
@@ -83,12 +75,13 @@ const getTransporter = (smptConfig: SMTPConfig) => {
83
75
  });
84
76
 
85
77
  transporter = nodemailer.createTransport({
86
- SES: { ses, aws },
87
- maxConnections: 1,
88
- sendingRate,
78
+ SES: {
79
+ sesClient,
80
+ SendEmailCommand,
81
+ },
89
82
  });
90
83
  } else {
91
- const { user, pass, host, port, secure, tls } = smptConfig;
84
+ const { user, pass, host, port, secure, tls } = smtpConfig;
92
85
  transporter = nodemailer.createTransport({
93
86
  host,
94
87
  port,
@@ -126,8 +119,8 @@ const send = (transporter: Transporter, email: Email) => {
126
119
  });
127
120
  };
128
121
 
129
- export const verifySMTPConfig = async (smptConfig: SMTPConfig) => {
130
- const transporter = getOrSetTransporter(smptConfig);
122
+ export const verifySMTPConfig = async (smtpConfig: SMTPConfig) => {
123
+ const transporter = getOrSetTransporter(smtpConfig);
131
124
  return new Promise((resolve, reject) => {
132
125
  transporter.verify((err, success) => {
133
126
  if (err) {
@@ -34,6 +34,7 @@ import { insert } from "./insert/insert";
34
34
  import { update } from "./update";
35
35
  import { updateBatch } from "./updateBatch";
36
36
  import { upsert } from "./upsert";
37
+ import { isApplicableHook } from "./isApplicableHook";
37
38
 
38
39
  export type ValidatedParams = {
39
40
  row: AnyObject;
@@ -71,7 +72,49 @@ export class TableHandler extends ViewHandler {
71
72
  this.is_media = dboBuilder.prostgles.isMedia(this.name);
72
73
  }
73
74
 
74
- getHooksAndChecks = (
75
+ getBeforeHooks = (command: "update" | "insert", rows: AnyObject[]) => {
76
+ return (
77
+ this.config?.hooks?.beforeEach?.filter((hook) =>
78
+ isApplicableHook(this, rows, hook, command),
79
+ ) ?? []
80
+ );
81
+ };
82
+
83
+ beforeEach = async (
84
+ row: AnyObject,
85
+ localParams: LocalParams | undefined,
86
+ command: "insert" | "update",
87
+ ) => {
88
+ const transaction = this.getTransaction(localParams);
89
+ const hooks = this.getBeforeHooks(command, [row]);
90
+ let newRow = row;
91
+ const successCallbacks: (() => void)[] = [];
92
+ for (const hook of hooks) {
93
+ const isApplicable = isApplicableHook(this, [newRow], hook, command);
94
+ if (!isApplicable) continue;
95
+ const hookResult = await hook.validate({
96
+ row: newRow,
97
+ command: "insert",
98
+ data: newRow,
99
+ dbx: this.getFinalDbo(localParams),
100
+ localParams,
101
+ tx: transaction?.t || this.db,
102
+ });
103
+ if (hookResult) {
104
+ newRow = hookResult.row;
105
+ const { onInserted } = hookResult;
106
+ if (onInserted) {
107
+ successCallbacks.push(onInserted);
108
+ }
109
+ }
110
+ }
111
+ return {
112
+ row: newRow,
113
+ successCallbacks,
114
+ };
115
+ };
116
+
117
+ getAfterHooksAndChecks = (
75
118
  command:
76
119
  | { name: "delete"; rule: undefined | DeleteRule }
77
120
  | { name: "update"; rule: undefined | UpdateRule }
@@ -109,8 +152,12 @@ export class TableHandler extends ViewHandler {
109
152
  .filter(isDefined);
110
153
  return [
111
154
  ...(afterEachHooks ?? []),
112
- ...(postValidate ? [{ type: "postValidate", validate: postValidate } as const] : []),
113
- ...(checkFilter ? [{ type: "checkFilter", checkFilter } as const] : []),
155
+ ...(postValidate ?
156
+ [{ type: "postValidate", validate: postValidate, changedFields: undefined } as const]
157
+ : []),
158
+ ...(checkFilter ?
159
+ [{ type: "checkFilter", checkFilter, changedFields: undefined } as const]
160
+ : []),
114
161
  ...(afterAllHooks ?? []),
115
162
  ];
116
163
  };
@@ -120,10 +167,17 @@ export class TableHandler extends ViewHandler {
120
167
  | { name: "update"; rule: undefined | UpdateRule }
121
168
  | { name: "insert"; rule: undefined | InsertRule },
122
169
  localParams: LocalParams | undefined,
170
+ newRows: AnyObject[],
123
171
  ) => {
124
172
  const transaction = this.getTransaction(localParams);
125
- const hasAfterChecks = this.getHooksAndChecks(command, localParams).length > 0;
126
- return { shouldWrap: !transaction && hasAfterChecks, hasAfterChecks };
173
+ const hasAfterChecks = this.getAfterHooksAndChecks(command, localParams).length > 0;
174
+ const hasBeforeHooks =
175
+ command.name !== "delete" && this.getBeforeHooks(command.name, newRows).length > 0;
176
+ return {
177
+ shouldWrap: !transaction && (hasAfterChecks || hasBeforeHooks),
178
+ hasBeforeHooks,
179
+ hasAfterChecks,
180
+ };
127
181
  };
128
182
  getFinalDbo = (localParams: LocalParams | undefined) => {
129
183
  return this.getTransaction(localParams)?.dbTX ?? this.dboBuilder.dbo;
@@ -11,7 +11,7 @@ import {
11
11
  import type { TableHandler } from "./TableHandler";
12
12
  import { onDeleteFromFileTable } from "./onDeleteFromFileTable";
13
13
  import { getReturnTypeQuery } from "../ViewHandler/getReturnTypeQuery";
14
- import { executeHooksCheckAndPostValidation } from "./executeHooksCheckAndPostValidation";
14
+ import { executeAfterHooksCheckAndPostValidation } from "./executeAfterHooksCheckAndPostValidation";
15
15
 
16
16
  export async function _delete(
17
17
  this: TableHandler,
@@ -28,7 +28,7 @@ export async function _delete(
28
28
  this.checkFilter(filter);
29
29
 
30
30
  const operation = { name: "delete", rule: tableRules?.delete } as const;
31
- const { hasAfterChecks, shouldWrap } = this.shouldWrapInTx(operation, localParams);
31
+ const { hasAfterChecks, shouldWrap } = this.shouldWrapInTx(operation, localParams, []);
32
32
  if (shouldWrap) {
33
33
  return this.dboBuilder.getTX((t) =>
34
34
  (t[this.name] as Partial<typeof this> | undefined)?.delete?.(
@@ -183,7 +183,7 @@ export async function _delete(
183
183
  return fullRow;
184
184
  });
185
185
 
186
- await executeHooksCheckAndPostValidation({
186
+ await executeAfterHooksCheckAndPostValidation({
187
187
  tableHandler: this,
188
188
  operation,
189
189
  localParams,
@@ -3,8 +3,9 @@ import type { DeleteRule, InsertRule, UpdateRule } from "../../PublishParser/Pub
3
3
  import { isArray } from "../../utils/utils";
4
4
  import type { LocalParams } from "../DboBuilder";
5
5
  import type { TableHandler } from "./TableHandler";
6
+ import { isApplicableHook } from "./isApplicableHook";
6
7
 
7
- export const executeHooksCheckAndPostValidation = async ({
8
+ export const executeAfterHooksCheckAndPostValidation = async ({
8
9
  tableHandler,
9
10
  operation,
10
11
  data,
@@ -22,22 +23,13 @@ export const executeHooksCheckAndPostValidation = async ({
22
23
  }) => {
23
24
  const command = operation.name;
24
25
  const transaction = tableHandler.getTransaction(localParams);
25
- const hooks = tableHandler.getHooksAndChecks(operation, localParams);
26
- let changedFieldsSet = undefined as undefined | Set<string>;
27
- const getChangedFieldsSet = () => {
28
- changedFieldsSet ??= new Set<string>(
29
- (isArray(data) ? data : [data]).map((row) => Object.keys(row)).flat(),
30
- );
31
- return changedFieldsSet;
32
- };
26
+ const hooks = tableHandler.getAfterHooksAndChecks(operation, localParams);
27
+ const newRows = isArray(data) ? data : [data];
28
+
33
29
  const applicableHooks = hooks.filter((hook) => {
34
30
  if (hook.type === "checkFilter") return false;
35
31
  if (hook.type === "postValidate") return true;
36
- const { commands, changedFields } = hook;
37
- return (
38
- commands[command] &&
39
- (!changedFields || changedFields.some((f) => getChangedFieldsSet().has(f)))
40
- );
32
+ return isApplicableHook(tableHandler, newRows, hook, command);
41
33
  });
42
34
 
43
35
  if (applicableHooks.length) {
@@ -58,6 +50,13 @@ export const executeHooksCheckAndPostValidation = async ({
58
50
  data,
59
51
  } as const;
60
52
  for (const hook of applicableHooks) {
53
+ const isApplicable = isApplicableHook(
54
+ tableHandler,
55
+ [row],
56
+ { commands: { [command]: 1 }, changedFields: hook.changedFields },
57
+ command,
58
+ );
59
+ if (!isApplicable) continue;
61
60
  if (hook.type === "afterEach") {
62
61
  await hook.validate({
63
62
  ...commonParams,
@@ -74,12 +73,22 @@ export const executeHooksCheckAndPostValidation = async ({
74
73
  }
75
74
 
76
75
  for (const hook of applicableHooks) {
76
+ const applicableRows = newRows.filter((row) => {
77
+ const isApplicable = isApplicableHook(
78
+ tableHandler,
79
+ [row],
80
+ { commands: { [command]: 1 }, changedFields: hook.changedFields },
81
+ command,
82
+ );
83
+ return isApplicable;
84
+ });
85
+ if (!applicableRows.length) continue;
77
86
  if (hook.type === "afterAll") {
78
87
  await hook.validate({
79
88
  ...txParams,
80
89
  command,
81
- data: Array.isArray(data) ? data : [data],
82
- rows,
90
+ data: newRows,
91
+ rows: applicableRows,
83
92
  localParams,
84
93
  });
85
94
  }
@@ -14,6 +14,7 @@ import { insertTest } from "../insertTest";
14
14
  import { runInsertUpdateQuery } from "../runInsertUpdateQuery";
15
15
  import { insertNestedRecords } from "./insertNestedRecords";
16
16
  import { getReturnTypeQuery } from "../../ViewHandler/getReturnTypeQuery";
17
+ import { isArray } from "../../../utils/utils";
17
18
 
18
19
  export async function insert(
19
20
  this: TableHandler,
@@ -34,7 +35,13 @@ export async function insert(
34
35
 
35
36
  const finalDBtx = this.getTransaction(localParams);
36
37
  /** Post validate and checkFilter require a transaction dbo handler because they need the action result */
37
- if (this.shouldWrapInTx({ name: ACTION, rule }, localParams).shouldWrap) {
38
+ if (
39
+ this.shouldWrapInTx(
40
+ { name: ACTION, rule },
41
+ localParams,
42
+ isArray(rowOrRows) ? rowOrRows : [rowOrRows],
43
+ ).shouldWrap
44
+ ) {
38
45
  return this.dboBuilder.getTX((t) =>
39
46
  t[this.name]?.[ACTION](rowOrRows, insertParams, param3_unused, tableRules, localParams),
40
47
  );
@@ -59,8 +66,8 @@ export async function insert(
59
66
  throw `Direct inserts not allowed. Only nested inserts from these tables: ${JSON.stringify(allowedNestedInserts)} `;
60
67
  }
61
68
  }
62
- const isMultiInsert = Array.isArray(rowOrRows);
63
- const rows = isMultiInsert ? (rowOrRows as AnyObject[]) : [rowOrRows];
69
+ const isMultiInsert = isArray(rowOrRows);
70
+ const rows = isMultiInsert ? rowOrRows : [rowOrRows];
64
71
 
65
72
  requiredNestedInserts?.forEach(({ ftable, maxRows, minRows }) => {
66
73
  if (this.column_names.includes(ftable))
@@ -69,7 +76,7 @@ export async function insert(
69
76
  const nestedInsert = row[ftable] as unknown;
70
77
  const nestedInsertRows =
71
78
  isObject(nestedInsert) ? [nestedInsert]
72
- : Array.isArray(nestedInsert) ? nestedInsert
79
+ : isArray(nestedInsert) ? nestedInsert
73
80
  : [];
74
81
  if (!nestedInsertRows.length) {
75
82
  throw `Missing required nested insert on rowId ${rowId} for ftable: ${ftable}`;
@@ -85,15 +92,17 @@ export async function insert(
85
92
 
86
93
  validateInsertParams(insertParams);
87
94
 
88
- // const tx = localParams?.tx?.t || this.tx?.t;
89
95
  const transaction = this.getTransaction(localParams);
90
96
  const tx = transaction?.t || this.db;
91
97
 
98
+ const successCallbacks: (() => void)[] = [];
92
99
  const preValidatedRows = await Promise.all(
93
100
  rows.map(async (nonValidated) => {
94
101
  const { preValidate, validate } = rule ?? {};
95
102
  const { tableConfigurator } = this.dboBuilder.prostgles;
96
- if (!tableConfigurator) throw "tableConfigurator missing";
103
+ if (!tableConfigurator) {
104
+ throw "tableConfigurator missing";
105
+ }
97
106
  let row = await tableConfigurator.getPreInsertRow(this, {
98
107
  dbx: this.getFinalDbo(localParams),
99
108
  validate,
@@ -103,6 +112,11 @@ export async function insert(
103
112
  command: "insert",
104
113
  data: nonValidated,
105
114
  });
115
+
116
+ const beforeResult = await this.beforeEach(row, localParams, "insert");
117
+ row = beforeResult.row;
118
+ beforeResult.successCallbacks.forEach((cb) => successCallbacks.push(cb));
119
+
106
120
  if (preValidate) {
107
121
  if (!localParams) {
108
122
  throw "localParams missing for insert preValidate";
@@ -269,7 +283,7 @@ export async function insert(
269
283
  data: { rowOrRows, param2: insertParams },
270
284
  duration: Date.now() - start,
271
285
  });
272
-
286
+ successCallbacks.forEach((cb) => cb());
273
287
  return result;
274
288
  } catch (e) {
275
289
  await this._log({
@@ -0,0 +1,28 @@
1
+ import type { AnyObject } from "prostgles-types";
2
+ import type { AfterEachTsTrigger } from "../../PublishParser/PublishParser";
3
+ import type { TableHandler } from "./TableHandler";
4
+
5
+ export const isApplicableHook = (
6
+ tableHandler: TableHandler,
7
+ rows: AnyObject[],
8
+ hook: Pick<AfterEachTsTrigger<AnyObject, any>, "commands" | "changedFields">,
9
+ command: "insert" | "update" | "delete",
10
+ ) => {
11
+ let changedFieldsSet = undefined as undefined | Set<string>;
12
+ const getChangedFieldsSet = () => {
13
+ changedFieldsSet ??= new Set(
14
+ tableHandler.column_names.filter((col) =>
15
+ rows.some((row) => row[col] !== undefined && row[col] !== null),
16
+ ),
17
+ );
18
+ return changedFieldsSet;
19
+ };
20
+
21
+ const { commands, changedFields } = hook;
22
+ return Boolean(
23
+ commands[command] &&
24
+ (command === "delete" ||
25
+ !changedFields ||
26
+ changedFields.some((f) => getChangedFieldsSet().has(f))),
27
+ );
28
+ };
@@ -5,7 +5,7 @@ import type { LocalParams } from "../DboBuilder";
5
5
  import { rejectWithPGClientError, withUserRLS } from "../DboBuilder";
6
6
  import type { TableHandler } from "./TableHandler";
7
7
  import { getSelectItemQuery } from "./TableHandler";
8
- import { executeHooksCheckAndPostValidation } from "./executeHooksCheckAndPostValidation";
8
+ import { executeAfterHooksCheckAndPostValidation } from "./executeAfterHooksCheckAndPostValidation";
9
9
 
10
10
  type RunInsertUpdateQueryArgs = {
11
11
  tableHandler: TableHandler;
@@ -126,7 +126,7 @@ export const runInsertUpdateQuery = async (args: RunInsertUpdateQueryArgs) => {
126
126
 
127
127
  const rows = result.modified ?? [];
128
128
 
129
- await executeHooksCheckAndPostValidation({
129
+ await executeAfterHooksCheckAndPostValidation({
130
130
  tableHandler,
131
131
  operation: command === "insert" ? { name: "insert", rule } : { name: "update", rule },
132
132
  localParams,
@@ -33,7 +33,7 @@ export async function update(
33
33
  ),
34
34
  );
35
35
  const rule = tableRules?.[ACTION];
36
- if (this.shouldWrapInTx({ name: ACTION, rule }, localParams).shouldWrap) {
36
+ if (this.shouldWrapInTx({ name: ACTION, rule }, localParams, [_newData]).shouldWrap) {
37
37
  return wrapInTx();
38
38
  }
39
39
 
@@ -76,6 +76,9 @@ export async function update(
76
76
  );
77
77
  }
78
78
 
79
+ const beforeResult = await this.beforeEach(newData, localParams, "update");
80
+ newData = beforeResult.row;
81
+
79
82
  const { data, allowedCols } = prepareNewData({
80
83
  row: newData,
81
84
  forcedData,
@@ -205,6 +208,7 @@ export async function update(
205
208
  data: { filter, _newData, params },
206
209
  duration: Date.now() - start,
207
210
  });
211
+ beforeResult.successCallbacks.forEach((cb) => cb());
208
212
  return result;
209
213
  } catch (e) {
210
214
  await this._log({
@@ -333,7 +333,7 @@ export const getFileType = async (
333
333
  >);
334
334
 
335
335
  const fileNameMime = getFileTypeFromFilename(fileName);
336
- if (!fileNameMime?.ext) throw new Error("File name must contain extenion");
336
+ if (!fileNameMime?.ext) throw new Error("File name must contain extension");
337
337
  const res = await fileTypeFromBuffer(typeof file === "string" ? Buffer.from(file, "utf8") : file);
338
338
 
339
339
  if (!res) {
@@ -55,6 +55,20 @@ export type UpdateRequestData<R extends AnyObject = AnyObject> =
55
55
  | UpdateRequestDataOne<R>
56
56
  | UpdateRequestDataBatch<R>;
57
57
 
58
+ export type ValidateBeforeRowArgsCommon<R = AnyObject, DBX = DBHandlerServer> = {
59
+ row: R;
60
+ dbx: DBX;
61
+ tx: pgPromise.ITask<{}> | DB;
62
+ } & (
63
+ | {
64
+ command: "insert";
65
+ data: R;
66
+ }
67
+ | {
68
+ command: "update";
69
+ data: Partial<R>;
70
+ }
71
+ );
58
72
  export type ValidateRowArgsCommon<R = AnyObject, DBX = DBHandlerServer> = {
59
73
  row: R;
60
74
  dbx: DBX;
@@ -74,9 +88,25 @@ export type ValidateRowArgsCommon<R = AnyObject, DBX = DBHandlerServer> = {
74
88
  }
75
89
  );
76
90
 
91
+ export type BeforeEachTsTrigger<R, DBX> = {
92
+ commands: Partial<Record<"insert" | "update", 1>>;
93
+ /**
94
+ * Will only run this trigger if the insert/update provides non null values for these fields.
95
+ */
96
+ changedFields?: (keyof R)[];
97
+ validate: (
98
+ params: ValidateBeforeRowArgsCommon<R, DBX> & {
99
+ localParams: undefined | LocalParams;
100
+ },
101
+ ) => Promise<void | { row: R; onInserted?: () => void }>;
102
+ };
103
+
77
104
  export type AfterEachTsTrigger<R, DBX> = {
78
105
  commands: Partial<Record<"insert" | "update" | "delete", 1>>;
79
- changedFields?: string[];
106
+ /**
107
+ * Will only run this trigger if the insert/update provides non null values for these fields.
108
+ */
109
+ changedFields?: (keyof R)[];
80
110
  validate: (
81
111
  params: ValidateRowArgsCommon<R, DBX> & {
82
112
  localParams: undefined | LocalParams;
@@ -16,10 +16,10 @@ import type { DB, DBHandlerServer, Prostgles } from "../Prostgles";
16
16
  import type {
17
17
  AfterAllTsTrigger,
18
18
  AfterEachTsTrigger,
19
+ BeforeEachTsTrigger,
19
20
  InsertRule,
20
21
  SyncConfig,
21
22
  ValidateRowArgsCommon,
22
- ValidateRowsArgsCommon,
23
23
  } from "../PublishParser/PublishParser";
24
24
  import {
25
25
  DEFAULT_SYNC_BATCH_SIZE,
@@ -63,11 +63,12 @@ type BaseTableDefinition<R = AnyObject, DBX = DBHandlerServer> = {
63
63
  hooks?: {
64
64
  /**
65
65
  * Hook used to run custom logic before inserting a row.
66
- * The returned row must satisfy the table schema
66
+ * The returned row must satisfy the table schema.
67
67
  */
68
68
  getPreInsertRow?: (
69
69
  args: GetPreInsertRowArgs,
70
70
  ) => Promise<{ row: AnyObject; onInserted: Promise<void> }>;
71
+ beforeEach?: BeforeEachTsTrigger<R, DBX>[];
71
72
  afterEach?: AfterEachTsTrigger<R, DBX>[];
72
73
  afterAll?: AfterAllTsTrigger<R, DBX>[];
73
74
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prostgles-server",
3
- "version": "4.2.551",
3
+ "version": "4.2.554",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -38,8 +38,8 @@
38
38
  ],
39
39
  "homepage": "https://prostgles.com",
40
40
  "dependencies": {
41
- "@aws-sdk/client-ses": "^3.1069.0",
42
- "@aws-sdk/credential-provider-node": "^3.972.56",
41
+ "@aws-sdk/client-sesv2": "^3.1090.0",
42
+ "@aws-sdk/credential-provider-node": "^3.972.70",
43
43
  "@types/passport": "^1.0.17",
44
44
  "@types/passport-facebook": "^3.0.4",
45
45
  "@types/passport-github2": "^1.2.9",
@@ -47,7 +47,7 @@
47
47
  "@types/passport-microsoft": "^1.0.3",
48
48
  "check-disk-space": "^3.4.0",
49
49
  "file-type": "^18.7.0",
50
- "nodemailer": "^7.0.13",
50
+ "nodemailer": "^9.0.3",
51
51
  "passport": "^0.7.0",
52
52
  "passport-facebook": "^3.0.0",
53
53
  "passport-github2": "^0.1.12",
@@ -55,24 +55,24 @@
55
55
  "passport-microsoft": "^2.1.0",
56
56
  "passport-oauth2": "^1.8.0",
57
57
  "pg": "^8.21.0",
58
- "pg-cursor": "^2.20.0",
59
- "pg-promise": "^12.6.2",
58
+ "pg-cursor": "^2.21.0",
59
+ "pg-promise": "^12.7.0",
60
60
  "prostgles-types": "^4.0.264"
61
61
  },
62
62
  "devDependencies": {
63
63
  "@eslint/js": "^9.22.0",
64
64
  "@types/express": "^4.17.25",
65
65
  "@types/json-schema": "^7.0.15",
66
- "@types/node": "^22.19.21",
67
- "@types/nodemailer": "^6.4.24",
66
+ "@types/node": "^22.20.1",
67
+ "@types/nodemailer": "^8.0.1",
68
68
  "@types/pg": "^8.20.0",
69
69
  "@types/pg-cursor": "^2.7.2",
70
70
  "@types/sharp": "^0.30.5",
71
- "eslint": "^9.39.4",
71
+ "eslint": "^9.39.5",
72
72
  "eslint-plugin-security": "^3.0.1",
73
- "prettier": "^3.8.4",
73
+ "prettier": "^3.9.5",
74
74
  "socket.io": "^4.8.3",
75
75
  "typescript": "^5.9.3",
76
- "typescript-eslint": "^8.61.1"
76
+ "typescript-eslint": "^8.64.0"
77
77
  }
78
78
  }