prostgles-server 4.2.626 → 4.2.627

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 (75) hide show
  1. package/debug/prostgles-types-4.0.285.tgz +0 -0
  2. package/dist/Audit/getAuditTableRules.js +1 -1
  3. package/dist/Audit/getAuditTableRules.js.map +1 -1
  4. package/dist/DboBuilder/QueryBuilder/Functions/Functions.d.ts +3 -1
  5. package/dist/DboBuilder/QueryBuilder/Functions/Functions.d.ts.map +1 -1
  6. package/dist/DboBuilder/QueryBuilder/Functions/Functions.js +18 -11
  7. package/dist/DboBuilder/QueryBuilder/Functions/Functions.js.map +1 -1
  8. package/dist/DboBuilder/QueryBuilder/Functions/HASHING_FUNCTIONS.d.ts.map +1 -1
  9. package/dist/DboBuilder/QueryBuilder/Functions/HASHING_FUNCTIONS.js +16 -21
  10. package/dist/DboBuilder/QueryBuilder/Functions/HASHING_FUNCTIONS.js.map +1 -1
  11. package/dist/DboBuilder/QueryBuilder/Functions/utils.d.ts +1 -0
  12. package/dist/DboBuilder/QueryBuilder/Functions/utils.d.ts.map +1 -1
  13. package/dist/DboBuilder/QueryBuilder/Functions/utils.js +3 -1
  14. package/dist/DboBuilder/QueryBuilder/Functions/utils.js.map +1 -1
  15. package/dist/DboBuilder/QueryBuilder/QueryBuilder.d.ts +18 -2
  16. package/dist/DboBuilder/QueryBuilder/QueryBuilder.d.ts.map +1 -1
  17. package/dist/DboBuilder/QueryBuilder/QueryBuilder.js +54 -16
  18. package/dist/DboBuilder/QueryBuilder/QueryBuilder.js.map +1 -1
  19. package/dist/DboBuilder/QueryBuilder/getNewQuery.d.ts +1 -1
  20. package/dist/DboBuilder/QueryBuilder/getNewQuery.d.ts.map +1 -1
  21. package/dist/DboBuilder/QueryBuilder/getNewQuery.js +40 -6
  22. package/dist/DboBuilder/QueryBuilder/getNewQuery.js.map +1 -1
  23. package/dist/DboBuilder/QueryBuilder/parseJoinSelect.d.ts.map +1 -1
  24. package/dist/DboBuilder/QueryBuilder/parseJoinSelect.js +2 -12
  25. package/dist/DboBuilder/QueryBuilder/parseJoinSelect.js.map +1 -1
  26. package/dist/DboBuilder/TableHandler/onDeleteFromFileTable.d.ts.map +1 -1
  27. package/dist/DboBuilder/TableHandler/onDeleteFromFileTable.js +7 -6
  28. package/dist/DboBuilder/TableHandler/onDeleteFromFileTable.js.map +1 -1
  29. package/dist/DboBuilder/ViewHandler/find.d.ts.map +1 -1
  30. package/dist/DboBuilder/ViewHandler/find.js +2 -29
  31. package/dist/DboBuilder/ViewHandler/find.js.map +1 -1
  32. package/dist/DboBuilder/ViewHandler/parseComplexFilter.d.ts +4 -1
  33. package/dist/DboBuilder/ViewHandler/parseComplexFilter.d.ts.map +1 -1
  34. package/dist/DboBuilder/ViewHandler/parseComplexFilter.js +9 -2
  35. package/dist/DboBuilder/ViewHandler/parseComplexFilter.js.map +1 -1
  36. package/dist/DboBuilder/ViewHandler/subscribe.d.ts.map +1 -1
  37. package/dist/DboBuilder/ViewHandler/subscribe.js +8 -3
  38. package/dist/DboBuilder/ViewHandler/subscribe.js.map +1 -1
  39. package/dist/DboBuilder/ViewHandler/validateSelectParams.d.ts +3 -0
  40. package/dist/DboBuilder/ViewHandler/validateSelectParams.d.ts.map +1 -0
  41. package/dist/DboBuilder/ViewHandler/validateSelectParams.js +48 -0
  42. package/dist/DboBuilder/ViewHandler/validateSelectParams.js.map +1 -0
  43. package/dist/DboBuilder/getCondition.d.ts.map +1 -1
  44. package/dist/DboBuilder/getCondition.js +9 -3
  45. package/dist/DboBuilder/getCondition.js.map +1 -1
  46. package/dist/DboBuilder/getSubscribeRelatedTables.d.ts.map +1 -1
  47. package/dist/DboBuilder/getSubscribeRelatedTables.js +7 -2
  48. package/dist/DboBuilder/getSubscribeRelatedTables.js.map +1 -1
  49. package/dist/PubSubManager/PubSubManager.d.ts +1 -1
  50. package/dist/PubSubManager/PubSubManager.d.ts.map +1 -1
  51. package/dist/PubSubManager/PubSubManager.js +1 -1
  52. package/dist/PubSubManager/PubSubManager.js.map +1 -1
  53. package/dist/PubSubManager/addSub.js +1 -1
  54. package/dist/PubSubManager/addSub.js.map +1 -1
  55. package/dist/TableConfig/initTableConfig.d.ts.map +1 -1
  56. package/dist/TableConfig/initTableConfig.js +1 -4
  57. package/dist/TableConfig/initTableConfig.js.map +1 -1
  58. package/lib/Audit/getAuditTableRules.ts +1 -1
  59. package/lib/DboBuilder/QueryBuilder/Functions/Functions.ts +38 -13
  60. package/lib/DboBuilder/QueryBuilder/Functions/HASHING_FUNCTIONS.ts +28 -24
  61. package/lib/DboBuilder/QueryBuilder/Functions/utils.ts +10 -0
  62. package/lib/DboBuilder/QueryBuilder/QueryBuilder.ts +95 -18
  63. package/lib/DboBuilder/QueryBuilder/getNewQuery.ts +58 -10
  64. package/lib/DboBuilder/QueryBuilder/parseJoinSelect.ts +2 -13
  65. package/lib/DboBuilder/TableHandler/onDeleteFromFileTable.ts +9 -8
  66. package/lib/DboBuilder/ViewHandler/find.ts +2 -32
  67. package/lib/DboBuilder/ViewHandler/parseComplexFilter.ts +10 -2
  68. package/lib/DboBuilder/ViewHandler/subscribe.ts +10 -3
  69. package/lib/DboBuilder/ViewHandler/validateSelectParams.ts +51 -0
  70. package/lib/DboBuilder/getCondition.ts +11 -3
  71. package/lib/DboBuilder/getSubscribeRelatedTables.ts +10 -3
  72. package/lib/PubSubManager/PubSubManager.ts +1 -1
  73. package/lib/PubSubManager/addSub.ts +1 -1
  74. package/lib/TableConfig/initTableConfig.ts +1 -6
  75. package/package.json +2 -2
@@ -4,13 +4,15 @@
4
4
  *--------------------------------------------------------------------------------------------*/
5
5
 
6
6
  import type {
7
+ AnyObject,
7
8
  ColumnInfo,
9
+ OrderBy,
8
10
  PG_COLUMN_UDT_DATA_TYPE,
9
11
  Select,
10
12
  ValidatedColumnInfo,
11
13
  } from "prostgles-types";
12
- import { getKeys, isEmpty, isObject, postgresToTsType } from "prostgles-types";
13
- import type { PGIdentifier, SortItem } from "../DboBuilder";
14
+ import { getKeys, includes, isEmpty, isObject, postgresToTsType } from "prostgles-types";
15
+ import type { ExistsFilterConfig, PGIdentifier, SortItem } from "../DboBuilder";
14
16
 
15
17
  import type { Awaitable } from "../../PublishParser/publishTypesAndUtils";
16
18
  import { asNameAlias } from "../../utils/asNameAlias";
@@ -30,6 +32,8 @@ export type SelectItem = {
30
32
  tsDataType?: ValidatedColumnInfo["tsDataType"];
31
33
  alias: string;
32
34
  selected: boolean;
35
+ dependencyFields?: string[];
36
+ dependencyExists?: ExistsFilterConfig[];
33
37
  } & (
34
38
  | {
35
39
  type: "column";
@@ -87,6 +91,36 @@ export const parseFunctionObject = (funcData: unknown): { funcName: string; args
87
91
  return { funcName, args };
88
92
  };
89
93
 
94
+ type AggregateOptions = {
95
+ $filter?: AnyObject;
96
+ $orderBy?: OrderBy;
97
+ };
98
+
99
+ type ParsedAggregateOptions = {
100
+ filter?: string;
101
+ orderBy?: string;
102
+ dependencyFields: string[];
103
+ dependencyExists: ExistsFilterConfig[];
104
+ };
105
+
106
+ const aggregateOptionKeys = ["$filter", "$orderBy"] as const;
107
+ const parseSelectFunctionObject = (funcData: Record<string, unknown>) => {
108
+ const functionData = Object.fromEntries(
109
+ Object.entries(funcData).filter(([key]) => !includes(aggregateOptionKeys, key)),
110
+ );
111
+ return {
112
+ ...parseFunctionObject(functionData),
113
+ aggregateOptions: {
114
+ ...(funcData.$filter !== undefined && {
115
+ $filter: funcData.$filter as AnyObject,
116
+ }),
117
+ ...(funcData.$orderBy !== undefined && {
118
+ $orderBy: funcData.$orderBy as OrderBy,
119
+ }),
120
+ } satisfies AggregateOptions,
121
+ };
122
+ };
123
+
90
124
  export class SelectItemBuilder {
91
125
  select: SelectItemValidated[] = [];
92
126
  private allFields: string[];
@@ -97,6 +131,7 @@ export class SelectItemBuilder {
97
131
  private functions: FunctionSpec[];
98
132
  private allowedFieldsIncludingComputed: string[];
99
133
  private columns: ColumnInfo[];
134
+ private parseAggregateOptions?: (options: AggregateOptions) => Promise<ParsedAggregateOptions>;
100
135
 
101
136
  constructor(params: {
102
137
  allowedFields: string[];
@@ -106,6 +141,7 @@ export class SelectItemBuilder {
106
141
  allFields: string[];
107
142
  isView: boolean;
108
143
  columns: ColumnInfo[];
144
+ parseAggregateOptions?: (options: AggregateOptions) => Promise<ParsedAggregateOptions>;
109
145
  }) {
110
146
  this.allFields = params.allFields;
111
147
  this.allowedFields = params.allowedFields;
@@ -113,6 +149,7 @@ export class SelectItemBuilder {
113
149
  this.computedFields = params.computedFields;
114
150
  this.functions = params.functions;
115
151
  this.columns = params.columns;
152
+ this.parseAggregateOptions = params.parseAggregateOptions;
116
153
  this.allowedFieldsIncludingComputed = this.allowedFields.concat(
117
154
  this.computedFields.map((cf) => cf.name),
118
155
  );
@@ -164,7 +201,12 @@ export class SelectItemBuilder {
164
201
  this.select.push({ ...item, fields });
165
202
  };
166
203
 
167
- private addFunction = (func: FunctionSpec | string, args: any[], alias: string) => {
204
+ private addFunction = (
205
+ func: FunctionSpec | string,
206
+ args: any[],
207
+ alias: string,
208
+ aggregateOptions: AggregateOptions = {},
209
+ ): void | Promise<void> => {
168
210
  const funcDef = parseFunction({
169
211
  func,
170
212
  args,
@@ -172,6 +214,27 @@ export class SelectItemBuilder {
172
214
  allowedFields: this.allowedFieldsIncludingComputed,
173
215
  });
174
216
 
217
+ if (Object.keys(aggregateOptions).length) {
218
+ if (funcDef.type !== "aggregation") {
219
+ throw `Aggregate options $filter and $orderBy are only allowed on aggregate functions`;
220
+ }
221
+ if (!this.parseAggregateOptions) {
222
+ throw "Aggregate options are not supported in this query";
223
+ }
224
+ return this.parseAggregateOptions(aggregateOptions).then((parsedOptions) => {
225
+ this.addFunctionItem(funcDef, args, alias, parsedOptions);
226
+ });
227
+ }
228
+
229
+ this.addFunctionItem(funcDef, args, alias);
230
+ };
231
+
232
+ private addFunctionItem = (
233
+ funcDef: FunctionSpec,
234
+ args: any[],
235
+ alias: string,
236
+ aggregateOptions?: ParsedAggregateOptions,
237
+ ) => {
175
238
  const fieldFilter = funcDef.getFields(args);
176
239
  this.addItem({
177
240
  type: funcDef.type,
@@ -184,11 +247,15 @@ export class SelectItemBuilder {
184
247
  args,
185
248
  tableAliasRaw: tableAlias,
186
249
  ctidField: undefined,
250
+ aggregateFilter: aggregateOptions?.filter,
251
+ aggregateOrderBy: aggregateOptions?.orderBy,
187
252
 
188
253
  /* CTID not available in AFTER trigger */
189
254
  // ctidField: this.isView? undefined : "ctid"
190
255
  }),
191
256
  selected: true,
257
+ dependencyFields: aggregateOptions?.dependencyFields,
258
+ dependencyExists: aggregateOptions?.dependencyExists,
192
259
  });
193
260
  };
194
261
 
@@ -204,7 +271,7 @@ export class SelectItemBuilder {
204
271
  singleColArg: false,
205
272
  getFields: (_args: any[]) => [],
206
273
  };
207
- this.addFunction(cf, [], compCol.name);
274
+ void this.addFunction(cf, [], compCol.name);
208
275
  return;
209
276
  }
210
277
  }
@@ -271,14 +338,16 @@ export class SelectItemBuilder {
271
338
  } else {
272
339
  await Promise.all(
273
340
  selectKeys.map(async (key) => {
274
- const val: unknown = userSelect[key as keyof typeof userSelect];
341
+ const userSelectValue: unknown = userSelect[key as keyof typeof userSelect];
275
342
  const throwErr: (message: string) => never = (message) => {
276
343
  console.trace(message);
277
- throw "Unexpected select -> " + JSON.stringify({ [key]: val }) + "\n" + message;
344
+ throw (
345
+ "Unexpected select -> " + JSON.stringify({ [key]: userSelectValue }) + "\n" + message
346
+ );
278
347
  };
279
348
 
280
349
  /* Included fields */
281
- if ([1, true].includes(val as number | boolean)) {
350
+ if (userSelectValue === 1 || userSelectValue === true) {
282
351
  if (key === "*") {
283
352
  this.allowedFields.map((key) => this.addColumn(key, true));
284
353
  } else {
@@ -286,18 +355,26 @@ export class SelectItemBuilder {
286
355
  }
287
356
 
288
357
  /* Aggregations and functions */
289
- } else if (typeof val === "string" || isObject(val)) {
358
+ } else if (typeof userSelectValue === "string" || isObject(userSelectValue)) {
359
+ const functionEntries =
360
+ isObject(userSelectValue) ?
361
+ Object.entries(userSelectValue).filter(
362
+ ([key]) => !includes(aggregateOptionKeys, key),
363
+ )
364
+ : [];
290
365
  /* Function shorthand notation
291
366
  { id: "$max" } === { id: { $max: ["id"] } } === SELECT MAX(id) AS id
292
367
  */
293
368
  if (
294
- (typeof val === "string" && val !== "*") ||
295
- (isObject(val) &&
296
- Object.keys(val).length === 1 &&
297
- Array.isArray(Object.values(val)[0]))
369
+ (typeof userSelectValue === "string" && userSelectValue !== "*") ||
370
+ (isObject(userSelectValue) &&
371
+ functionEntries.length === 1 &&
372
+ Array.isArray(functionEntries[0]![1]))
298
373
  ) {
299
- let funcName: string | undefined, args: any[] | undefined;
300
- if (typeof val === "string") {
374
+ let funcName: string | undefined,
375
+ args: any[] | undefined,
376
+ aggregateOptions: AggregateOptions = {};
377
+ if (typeof userSelectValue === "string") {
301
378
  /* Shorthand notation -> it is expected that the key is the column name used as the only argument */
302
379
  try {
303
380
  this.checkField(key, true);
@@ -306,22 +383,22 @@ export class SelectItemBuilder {
306
383
  ` Shorthand function notation error: the specified column ( ${key} ) is invalid or disallowed. \n Use correct column name or full aliased function notation, e.g.: -> { alias: { $func_name: ["column_name"] } } `,
307
384
  );
308
385
  }
309
- funcName = val;
386
+ funcName = userSelectValue;
310
387
  args = [key];
311
388
 
312
389
  /** Function full notation { $funcName: ["colName", ...args] } */
313
390
  } else {
314
- ({ funcName, args } = parseFunctionObject(val));
391
+ ({ funcName, args, aggregateOptions } = parseSelectFunctionObject(userSelectValue));
315
392
  }
316
393
 
317
- this.addFunction(funcName, args, key);
394
+ await this.addFunction(funcName, args, key, aggregateOptions);
318
395
 
319
396
  /* Join */
320
397
  } else {
321
398
  if (!joinParse) {
322
399
  throw "Joins disallowed";
323
400
  }
324
- const joinSelect = val;
401
+ const joinSelect = userSelectValue;
325
402
 
326
403
  const parsedJoin = parseJoinSelect(joinSelect);
327
404
 
@@ -1,7 +1,13 @@
1
1
  import type { SelectParams } from "prostgles-types";
2
- import { asName, isEmpty, omitKeys } from "prostgles-types";
2
+ import { asName, isEmpty, isObject, omitKeys } from "prostgles-types";
3
3
  import type { ParsedTableRule } from "../../PublishParser/PublishParser";
4
- import type { Filter, LocalParams, PGIdentifier, ValidatedTableRules } from "../DboBuilder";
4
+ import {
5
+ prepareOrderByQuery,
6
+ type Filter,
7
+ type LocalParams,
8
+ type PGIdentifier,
9
+ type ValidatedTableRules,
10
+ } from "../DboBuilder";
5
11
  import type { ViewHandler } from "../ViewHandler/ViewHandler";
6
12
  import { parseJoinPath } from "../ViewHandler/parseJoinPath";
7
13
  import { prepareSortItems } from "../ViewHandler/prepareSortItems";
@@ -21,22 +27,31 @@ export const getNewQuery = async (
21
27
  ): Promise<NewQuery> => {
22
28
  const { columns } = viewHandler;
23
29
 
24
- if (localParams?.isRemoteRequest && !tableRules?.select?.fields) {
30
+ const selectRule = tableRules?.select;
31
+ if (tableRules && !selectRule?.fields) {
25
32
  throw `INTERNAL ERROR: publish.${viewHandler.name}.select.fields rule missing`;
26
33
  }
27
34
 
35
+ /** Only missing tableRules means this is an unrestricted local query */
28
36
  const allowedOrderByFields =
29
- !tableRules ?
37
+ !selectRule ?
30
38
  viewHandler.column_names.slice(0)
31
- : viewHandler.parseFieldFilter(tableRules.select?.orderByFields ?? tableRules.select?.fields);
39
+ : viewHandler.parseFieldFilter(selectRule.orderByFields);
32
40
  const allowedSelectFields =
33
- !tableRules ?
41
+ !selectRule ?
34
42
  viewHandler.column_names.slice(0)
35
- : viewHandler.parseFieldFilter(tableRules.select?.fields);
43
+ : viewHandler.parseFieldFilter(selectRule.fields);
44
+ const allowedFilterFields =
45
+ !selectRule ?
46
+ viewHandler.column_names.slice(0)
47
+ : viewHandler.parseFieldFilter(selectRule.filterFields);
36
48
 
37
49
  const joinQueries: NewQueryJoin[] = [];
38
50
 
39
51
  const { select: userSelect = "*" } = selectParams;
52
+ const aggregateTableAlias =
53
+ selectParams.joinExpressionAlias ??
54
+ ({ raw: ROOT_TABLE_ALIAS, escaped: ROOT_TABLE_ALIAS } satisfies PGIdentifier);
40
55
 
41
56
  const selectItemBuilder = new SelectItemBuilder({
42
57
  allowedFields: allowedSelectFields,
@@ -46,6 +61,39 @@ export const getNewQuery = async (
46
61
  functions: FUNCTIONS,
47
62
  allFields: viewHandler.column_names.slice(0),
48
63
  columns,
64
+ parseAggregateOptions: async ({ $filter, $orderBy }) => {
65
+ if ($filter !== undefined && !isObject($filter)) {
66
+ throw `Invalid aggregate $filter. Expecting an object`;
67
+ }
68
+ const filterInfo =
69
+ $filter === undefined ? undefined : (
70
+ await prepareWhere(viewHandler, {
71
+ filter: $filter,
72
+ select: undefined,
73
+ filterFields: allowedFilterFields,
74
+ tableAlias: aggregateTableAlias,
75
+ localParams,
76
+ addWhere: false,
77
+ tableRule: tableRules,
78
+ })
79
+ );
80
+ const orderByItems = prepareSortItems(
81
+ $orderBy,
82
+ allowedOrderByFields,
83
+ aggregateTableAlias,
84
+ [],
85
+ [],
86
+ );
87
+ return {
88
+ filter: filterInfo?.condition,
89
+ orderBy: prepareOrderByQuery(orderByItems, aggregateTableAlias.raw)[0],
90
+ dependencyFields: [
91
+ ...(filterInfo?.columnsUsed ?? []),
92
+ ...orderByItems.map(({ key }) => key),
93
+ ],
94
+ dependencyExists: filterInfo?.exists ?? [],
95
+ };
96
+ },
49
97
  });
50
98
 
51
99
  await selectItemBuilder.parseUserSelectWithJoins(
@@ -151,9 +199,9 @@ export const getNewQuery = async (
151
199
  ...omitKeys(commonWhereParams, ["forcedFilter", "selectParams"]),
152
200
  filter: selectParams.having,
153
201
  tableAlias:
154
- selectParams.joinExpressionAlias ? source.alias : (
155
- { raw: ROOT_TABLE_ALIAS, escaped: ROOT_TABLE_ALIAS }
156
- ),
202
+ selectParams.joinExpressionAlias ?
203
+ source.alias
204
+ : { raw: ROOT_TABLE_ALIAS, escaped: ROOT_TABLE_ALIAS },
157
205
  isHaving: true,
158
206
  })
159
207
  );
@@ -5,18 +5,7 @@ import type {
5
5
  RawJoinPath,
6
6
  SimpleJoinSelect,
7
7
  } from "prostgles-types";
8
- import { getKeys, includes } from "prostgles-types";
9
-
10
- const JOIN_KEYS = ["$innerJoin", "$leftJoin"] as const;
11
- const JOIN_PARAM_KEYS = getKeys({
12
- $condition: 1,
13
- filter: 1,
14
- having: 1,
15
- limit: 1,
16
- offset: 1,
17
- orderBy: 1,
18
- select: 1,
19
- } satisfies Record<keyof Omit<DetailedJoinSelect, (typeof JOIN_KEYS)[number]>, 1>);
8
+ import { getKeys, includes, JOIN_KEYS, JOIN_PARAMS } from "prostgles-types";
20
9
 
21
10
  export type ParsedJoin =
22
11
  | {
@@ -47,7 +36,7 @@ export const parseJoinSelect = (joinParams: JoinSelect): ParsedJoin | string =>
47
36
  } else if (joinKey) {
48
37
  /* Full option join { field_name: db.innerJoin.table_name(filter, select) } */
49
38
  const invalidParams = Object.keys(joinParams).filter(
50
- (k) => !includes([...JOIN_PARAM_KEYS, ...JOIN_KEYS], k),
39
+ (k) => !includes([...JOIN_PARAMS, ...JOIN_KEYS], k),
51
40
  );
52
41
  if (invalidParams.length) {
53
42
  throw "Invalid join params: " + invalidParams.join(", ");
@@ -36,32 +36,33 @@ export const onDeleteFromFileTable = async (
36
36
 
37
37
  const fileIds =
38
38
  config.versioning ?
39
- ((await getFileTableHandler(dbx, config).find(filterOpts.filter, {
39
+ await getFileTableHandler(dbx, config).find(filterOpts.filter, {
40
40
  select: { id: 1 },
41
41
  limit: null,
42
- })) as { id: string }[])
42
+ })
43
43
  : [];
44
44
  const versionFiles =
45
45
  fileIds.length ?
46
- ((await getFileRevisionsTableHandler(dbx, config).find(
46
+ await getFileRevisionsTableHandler(dbx, config).find(
47
47
  { file_id: { $in: fileIds.map(({ id }) => id) } },
48
48
  { select: { storage_key: 1 }, limit: null },
49
- )) as { storage_key: string }[])
49
+ )
50
50
  : [];
51
51
 
52
- const files = await t.any<Record<string, unknown> & { _prostgles_storage_key: string }>(
52
+ const KEY = "_prostgles_storage_key" as const;
53
+ const files = await t.any<Record<string, unknown> & { [KEY]: string }>(
53
54
  `DELETE FROM ${asName(tableName)} ${filterOpts.where}
54
55
  ${returningQuery ? `${returningQuery},` : "RETURNING"}
55
- COALESCE(storage_key, id) AS _prostgles_storage_key`,
56
+ COALESCE(storage_key, id) AS ${KEY}`,
56
57
  );
57
58
  const storageKeys = new Set([
58
59
  ...versionFiles.map(({ storage_key }) => storage_key),
59
- ...files.map(({ _prostgles_storage_key }) => _prostgles_storage_key),
60
+ ...files.map((row) => row[KEY]),
60
61
  ]);
61
62
  for (const storageKey of storageKeys) {
62
63
  onCommit(({ db }) => deleteUnreferencedFile(db, config, storageKey));
63
64
  }
64
- const result = files.map(({ _prostgles_storage_key, ...row }) => row);
65
+ const result = files.map(({ [KEY]: _, ...row }) => row);
65
66
  if (!returningQuery) return undefined;
66
67
  return (isOneOrNone ? result[0] : result) as AnyObject[] | undefined;
67
68
  };
@@ -11,6 +11,7 @@ import {
11
11
  import { getNewQuery } from "../QueryBuilder/getNewQuery";
12
12
  import { getSelectQuery } from "../QueryBuilder/getSelectQuery";
13
13
  import { getReturnTypeQuery } from "./getReturnTypeQuery";
14
+ import { validateSelectParams } from "./validateSelectParams";
14
15
  import type { ViewHandler } from "./ViewHandler";
15
16
 
16
17
  export type Param3 = {
@@ -30,44 +31,13 @@ export const find = async function (
30
31
 
31
32
  const command = limit === 1 && returnType === "row" ? "findOne" : "find";
32
33
  try {
33
- const allowedReturnTypes = Object.keys({
34
- row: 1,
35
- statement: 1,
36
- value: 1,
37
- values: 1,
38
- "statement-no-rls": 1,
39
- "statement-where": 1,
40
- } satisfies Record<Required<SelectParams>["returnType"], 1>);
34
+ validateSelectParams(selectParams);
41
35
 
42
36
  const { returnType } = selectParams || {};
43
- if (returnType && !allowedReturnTypes.includes(returnType)) {
44
- throw `returnType (${returnType}) can only be ${allowedReturnTypes.join(" OR ")}`;
45
- }
46
37
 
47
38
  const { testRule = false } = localParams || {};
48
39
 
49
40
  if (testRule) return [];
50
- if (selectParams) {
51
- const validParamNames = Object.keys({
52
- select: 1,
53
- orderBy: 1,
54
- offset: 1,
55
- limit: 1,
56
- returnType: 1,
57
- groupBy: 1,
58
- having: 1,
59
- abortSignal: 1,
60
- } satisfies Record<keyof SelectParams, 1>);
61
-
62
- const invalidParams = Object.keys(selectParams).filter((k) => !validParamNames.includes(k));
63
- if (invalidParams.length)
64
- throw (
65
- "Invalid params: " +
66
- invalidParams.join(", ") +
67
- " \n Expecting: " +
68
- validParamNames.join(", ")
69
- );
70
- }
71
41
 
72
42
  /* Validate publish */
73
43
  if (tableRules) {
@@ -34,6 +34,8 @@ export const parseComplexFilter = ({
34
34
  allowed_colnames,
35
35
  columns,
36
36
  }: Args) => {
37
+ const columnsUsed: string[] = [];
38
+
37
39
  /**
38
40
  * { $funcName: [arg1, arg2] }
39
41
  * { $column: "column_name" }
@@ -45,8 +47,9 @@ export const parseComplexFilter = ({
45
47
  throw `expecting: \n { $column: "column_name" } received:\n ${JSON.stringify(funcData)}`;
46
48
  }
47
49
  if (!allowed_colnames.includes(column)) {
48
- throw `Dissallowed or Invalid column ${column}. Allowed columns: ${allowed_colnames}`;
50
+ throw `Disallowed or Invalid column ${column}. Allowed columns: ${allowed_colnames}`;
49
51
  }
52
+ columnsUsed.push(column);
50
53
  return asNameAlias(column, tableAliasRaw);
51
54
  }
52
55
  const { funcName, args } = parseFunctionObject(funcData);
@@ -56,6 +59,8 @@ export const parseComplexFilter = ({
56
59
  functions: FUNCTIONS,
57
60
  allowedFields: allowed_colnames,
58
61
  });
62
+ const fields = funcDef.getFields(args);
63
+ columnsUsed.push(...(fields === "*" ? allowed_colnames : fields));
59
64
  return funcDef.getQuery({
60
65
  args,
61
66
  tableAliasRaw,
@@ -108,5 +113,8 @@ export const parseComplexFilter = ({
108
113
  result += ` ${sqlOperand} ${rightVal}`;
109
114
  }
110
115
 
111
- return result;
116
+ return {
117
+ condition: result,
118
+ columnsUsed: Array.from(new Set(columnsUsed)),
119
+ };
112
120
  };
@@ -77,9 +77,16 @@ async function subscribe(
77
77
  newQuery,
78
78
  });
79
79
 
80
- const [firstField, ...otherFields] = newQuery.select
81
- .filter((s) => s.selected)
82
- .flatMap((c) => c.fields);
80
+ const [firstField, ...otherFields] = Array.from(
81
+ new Set([
82
+ ...newQuery.select
83
+ .filter((s) => s.selected)
84
+ .flatMap((item) => [...item.fields, ...(item.dependencyFields ?? [])]),
85
+ ...newQuery.orderByItems
86
+ .map(({ key }) => key)
87
+ .filter((key) => this.column_names.includes(key)),
88
+ ]),
89
+ );
83
90
 
84
91
  const commonSubOpts = {
85
92
  table_info: this.tableOrViewInfo,
@@ -0,0 +1,51 @@
1
+ import { getJSONBObjectSchemaValidationError, getKeys, type SelectParams } from "prostgles-types";
2
+
3
+ export const validateSelectParams = (
4
+ selectParams: any,
5
+ ): selectParams is SelectParams | undefined => {
6
+ if (selectParams === undefined) {
7
+ return true;
8
+ }
9
+ const allowedReturnTypes = getKeys({
10
+ row: 1,
11
+ statement: 1,
12
+ value: 1,
13
+ values: 1,
14
+ "statement-no-rls": 1,
15
+ "statement-where": 1,
16
+ } satisfies Record<Required<SelectParams>["returnType"], 1>);
17
+
18
+ const selectParamsValidation = getJSONBObjectSchemaValidationError(
19
+ {
20
+ select: {
21
+ oneOf: [{ enum: ["*", ""] as const }, { record: { values: "any" } }, "string[]"],
22
+ optional: true,
23
+ },
24
+ orderBy: {
25
+ oneOf: [
26
+ { record: { values: "any" } },
27
+ { arrayOf: { record: { values: "any" } } },
28
+ "string[]",
29
+ "string",
30
+ ],
31
+ optional: true,
32
+ },
33
+ offset: { type: "integer", optional: true },
34
+ limit: { type: "integer", nullable: true, optional: true },
35
+ returnType: {
36
+ enum: allowedReturnTypes,
37
+ optional: true,
38
+ },
39
+ groupBy: { type: "boolean", optional: true },
40
+ having: { record: { values: "any" }, optional: true },
41
+ abortSignal: { type: "any", optional: true },
42
+ },
43
+ selectParams,
44
+ "selectParams",
45
+ );
46
+ if (selectParamsValidation.error !== undefined) {
47
+ throw `selectParams validation error: ${JSON.stringify(selectParamsValidation.error)}`;
48
+ }
49
+ selectParamsValidation.data satisfies SelectParams;
50
+ return true;
51
+ };
@@ -44,6 +44,11 @@ export async function getCondition(
44
44
  const filter = { ...rawFilter };
45
45
 
46
46
  const existsConfigs = getExistsFilters(filter, this);
47
+ const dependencyColumns = existsConfigs.flatMap((existsConfig) =>
48
+ existsConfig.isJoined ?
49
+ (existsConfig.parsedPath[0]?.on.flatMap((columnPair) => Object.keys(columnPair)) ?? [])
50
+ : [],
51
+ );
47
52
 
48
53
  const functionConditions: string[] = [];
49
54
  const funcFilter = FILTER_FUNCS.filter((f) => f.name in filter);
@@ -59,6 +64,7 @@ export async function getCondition(
59
64
  if (disallowedCols.length) {
60
65
  throw `Invalid/disallowed columns found in function filter: ${disallowedCols}`;
61
66
  }
67
+ dependencyColumns.push(...fields);
62
68
  functionConditions.push(
63
69
  f.getQuery({
64
70
  args: funcArgs,
@@ -89,6 +95,7 @@ export async function getCondition(
89
95
  const compCol = computedFields.find((cf) => cf.name === key);
90
96
  if (compCol) {
91
97
  if (!p.select) throw new Error("Computed column filter requires p.select.fields");
98
+ dependencyColumns.push(...p.select.fields);
92
99
  computedColConditions.push(
93
100
  compCol.getQuery({
94
101
  tableAliasRaw: tableAlias?.raw,
@@ -157,14 +164,15 @@ export async function getCondition(
157
164
  const complexFilters: string[] = [];
158
165
  const complexFilterKey = "$filter";
159
166
  if (complexFilterKey in filter) {
160
- const complexFilterCondition = parseComplexFilter({
167
+ const complexFilter = parseComplexFilter({
161
168
  filter,
162
169
  complexFilterKey,
163
170
  tableAliasRaw: tableAlias?.raw,
164
171
  allowed_colnames,
165
172
  columns: this.columns,
166
173
  });
167
- complexFilters.push(complexFilterCondition);
174
+ complexFilters.push(complexFilter.condition);
175
+ dependencyColumns.push(...complexFilter.columnsUsed);
168
176
  }
169
177
 
170
178
  /* Parse join filters
@@ -238,7 +246,7 @@ export async function getCondition(
238
246
 
239
247
  /* sorted to ensure duplicate subscription channels are not created due to different condition order */
240
248
  return {
241
- columnsUsed: q?.columnsUsed ?? [],
249
+ columnsUsed: Array.from(new Set([...(q?.columnsUsed ?? []), ...dependencyColumns])),
242
250
  exists: existsConfigs,
243
251
  condition: templates.sort().join(" AND \n"),
244
252
  };
@@ -1,5 +1,5 @@
1
1
  import type { AnyObject, ParsedJoinPath, SubscribeParams } from "prostgles-types";
2
- import { asName, isDefined, isEmpty, reverseParsedPath } from "prostgles-types";
2
+ import { asName, isEmpty, reverseParsedPath } from "prostgles-types";
3
3
  import type { ParsedTableRule } from "../PublishParser/PublishParser";
4
4
  import type { ViewSubscriptionOptions } from "../PubSubManager/PubSubManager";
5
5
  import type { Filter, LocalParams } from "./DboBuilder";
@@ -36,6 +36,9 @@ export async function getSubscribeRelatedTables(
36
36
  relatedTables: [],
37
37
  };
38
38
 
39
+ const dependencyExists = newQuery.select
40
+ .filter((item) => item.selected)
41
+ .flatMap((item) => item.dependencyExists ?? []);
39
42
  const nonExistsFilter = newQuery.whereOpts.exists.length ? {} : filter;
40
43
  const pushRelatedTable = async (
41
44
  relatedTableName: string,
@@ -112,11 +115,15 @@ export async function getSubscribeRelatedTables(
112
115
  await pushRelatedTable(
113
116
  j.table.raw,
114
117
  j.joinPath,
115
- j.select.map((s) => (s.selected ? s.columnName : undefined)).filter(isDefined),
118
+ j.select
119
+ .filter((item) => item.selected)
120
+ .flatMap((item) => [...item.fields, ...(item.dependencyFields ?? [])]),
116
121
  {},
117
122
  );
118
123
  }
119
- for (const existsFilter of newQuery.whereOpts.exists.filter((e) => e.isJoined)) {
124
+ for (const existsFilter of [...newQuery.whereOpts.exists, ...dependencyExists].filter(
125
+ (existsFilter) => existsFilter.isJoined,
126
+ )) {
120
127
  for (const [index, pathItem] of existsFilter.parsedPath.entries()) {
121
128
  const isLast = index === existsFilter.parsedPath.length - 1;
122
129
  await pushRelatedTable(
@@ -200,7 +200,7 @@ export class PubSubManager {
200
200
 
201
201
  subs: Subscription[] = [];
202
202
  syncs: SyncParams[] = [];
203
- readonly socketChannelPreffix = CHANNELS._preffix;
203
+ readonly socketChannelPrefix = CHANNELS._preffix;
204
204
  postgresNotifListenManager?: PostgresNotifListenManager;
205
205
 
206
206
  private constructor(dboBuilder: DboBuilder) {