prostgles-server 4.2.159 → 4.2.161

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 (107) hide show
  1. package/dist/Auth/setEmailProvider.js +2 -2
  2. package/dist/Auth/setEmailProvider.js.map +1 -1
  3. package/lib/Auth/AuthHandler.ts +436 -0
  4. package/lib/Auth/AuthTypes.ts +280 -0
  5. package/lib/Auth/getSafeReturnURL.ts +35 -0
  6. package/lib/Auth/sendEmail.ts +83 -0
  7. package/lib/Auth/setAuthProviders.ts +128 -0
  8. package/lib/Auth/setEmailProvider.ts +85 -0
  9. package/lib/Auth/setupAuthRoutes.ts +161 -0
  10. package/lib/DBEventsManager.ts +178 -0
  11. package/lib/DBSchemaBuilder.ts +225 -0
  12. package/lib/DboBuilder/DboBuilder.ts +319 -0
  13. package/lib/DboBuilder/DboBuilderTypes.ts +361 -0
  14. package/lib/DboBuilder/QueryBuilder/Functions.ts +1153 -0
  15. package/lib/DboBuilder/QueryBuilder/QueryBuilder.ts +288 -0
  16. package/lib/DboBuilder/QueryBuilder/getJoinQuery.ts +263 -0
  17. package/lib/DboBuilder/QueryBuilder/getNewQuery.ts +271 -0
  18. package/lib/DboBuilder/QueryBuilder/getSelectQuery.ts +136 -0
  19. package/lib/DboBuilder/QueryBuilder/prepareHaving.ts +22 -0
  20. package/lib/DboBuilder/QueryStreamer.ts +250 -0
  21. package/lib/DboBuilder/TableHandler/DataValidator.ts +428 -0
  22. package/lib/DboBuilder/TableHandler/TableHandler.ts +205 -0
  23. package/lib/DboBuilder/TableHandler/delete.ts +115 -0
  24. package/lib/DboBuilder/TableHandler/insert.ts +183 -0
  25. package/lib/DboBuilder/TableHandler/insertTest.ts +78 -0
  26. package/lib/DboBuilder/TableHandler/onDeleteFromFileTable.ts +62 -0
  27. package/lib/DboBuilder/TableHandler/runInsertUpdateQuery.ts +134 -0
  28. package/lib/DboBuilder/TableHandler/update.ts +126 -0
  29. package/lib/DboBuilder/TableHandler/updateBatch.ts +49 -0
  30. package/lib/DboBuilder/TableHandler/updateFile.ts +48 -0
  31. package/lib/DboBuilder/TableHandler/upsert.ts +34 -0
  32. package/lib/DboBuilder/ViewHandler/ViewHandler.ts +393 -0
  33. package/lib/DboBuilder/ViewHandler/count.ts +38 -0
  34. package/lib/DboBuilder/ViewHandler/find.ts +153 -0
  35. package/lib/DboBuilder/ViewHandler/getExistsCondition.ts +73 -0
  36. package/lib/DboBuilder/ViewHandler/getExistsFilters.ts +74 -0
  37. package/lib/DboBuilder/ViewHandler/getInfo.ts +32 -0
  38. package/lib/DboBuilder/ViewHandler/getTableJoinQuery.ts +84 -0
  39. package/lib/DboBuilder/ViewHandler/parseComplexFilter.ts +96 -0
  40. package/lib/DboBuilder/ViewHandler/parseFieldFilter.ts +105 -0
  41. package/lib/DboBuilder/ViewHandler/parseJoinPath.ts +208 -0
  42. package/lib/DboBuilder/ViewHandler/prepareSortItems.ts +163 -0
  43. package/lib/DboBuilder/ViewHandler/prepareWhere.ts +90 -0
  44. package/lib/DboBuilder/ViewHandler/size.ts +37 -0
  45. package/lib/DboBuilder/ViewHandler/subscribe.ts +118 -0
  46. package/lib/DboBuilder/ViewHandler/validateViewRules.ts +70 -0
  47. package/lib/DboBuilder/dboBuilderUtils.ts +222 -0
  48. package/lib/DboBuilder/getColumns.ts +114 -0
  49. package/lib/DboBuilder/getCondition.ts +201 -0
  50. package/lib/DboBuilder/getSubscribeRelatedTables.ts +190 -0
  51. package/lib/DboBuilder/getTablesForSchemaPostgresSQL.ts +426 -0
  52. package/lib/DboBuilder/insertNestedRecords.ts +355 -0
  53. package/lib/DboBuilder/parseUpdateRules.ts +187 -0
  54. package/lib/DboBuilder/prepareShortestJoinPaths.ts +186 -0
  55. package/lib/DboBuilder/runSQL.ts +182 -0
  56. package/lib/DboBuilder/runTransaction.ts +50 -0
  57. package/lib/DboBuilder/sqlErrCodeToMsg.ts +254 -0
  58. package/lib/DboBuilder/uploadFile.ts +69 -0
  59. package/lib/Event_Trigger_Tags.ts +118 -0
  60. package/lib/FileManager/FileManager.ts +358 -0
  61. package/lib/FileManager/getValidatedFileType.ts +69 -0
  62. package/lib/FileManager/initFileManager.ts +187 -0
  63. package/lib/FileManager/upload.ts +62 -0
  64. package/lib/FileManager/uploadStream.ts +79 -0
  65. package/lib/Filtering.ts +463 -0
  66. package/lib/JSONBValidation/validate_jsonb_schema_sql.ts +502 -0
  67. package/lib/JSONBValidation/validation.ts +143 -0
  68. package/lib/Logging.ts +127 -0
  69. package/lib/PostgresNotifListenManager.ts +143 -0
  70. package/lib/Prostgles.ts +485 -0
  71. package/lib/ProstglesTypes.ts +196 -0
  72. package/lib/PubSubManager/PubSubManager.ts +609 -0
  73. package/lib/PubSubManager/addSub.ts +138 -0
  74. package/lib/PubSubManager/addSync.ts +141 -0
  75. package/lib/PubSubManager/getCreatePubSubManagerError.ts +72 -0
  76. package/lib/PubSubManager/getPubSubManagerInitQuery.ts +662 -0
  77. package/lib/PubSubManager/initPubSubManager.ts +79 -0
  78. package/lib/PubSubManager/notifListener.ts +173 -0
  79. package/lib/PubSubManager/orphanTriggerCheck.ts +70 -0
  80. package/lib/PubSubManager/pushSubData.ts +55 -0
  81. package/lib/PublishParser/PublishParser.ts +162 -0
  82. package/lib/PublishParser/getFileTableRules.ts +124 -0
  83. package/lib/PublishParser/getSchemaFromPublish.ts +141 -0
  84. package/lib/PublishParser/getTableRulesWithoutFileTable.ts +177 -0
  85. package/lib/PublishParser/publishTypesAndUtils.ts +399 -0
  86. package/lib/RestApi.ts +127 -0
  87. package/lib/SchemaWatch/SchemaWatch.ts +90 -0
  88. package/lib/SchemaWatch/createSchemaWatchEventTrigger.ts +3 -0
  89. package/lib/SchemaWatch/getValidatedWatchSchemaType.ts +45 -0
  90. package/lib/SchemaWatch/getWatchSchemaTagList.ts +27 -0
  91. package/lib/SyncReplication.ts +557 -0
  92. package/lib/TableConfig/TableConfig.ts +468 -0
  93. package/lib/TableConfig/getColumnDefinitionQuery.ts +111 -0
  94. package/lib/TableConfig/getConstraintDefinitionQueries.ts +95 -0
  95. package/lib/TableConfig/getFutureTableSchema.ts +64 -0
  96. package/lib/TableConfig/getPGIndexes.ts +53 -0
  97. package/lib/TableConfig/getTableColumnQueries.ts +129 -0
  98. package/lib/TableConfig/initTableConfig.ts +326 -0
  99. package/lib/index.ts +13 -0
  100. package/lib/initProstgles.ts +319 -0
  101. package/lib/onSocketConnected.ts +102 -0
  102. package/lib/runClientRequest.ts +129 -0
  103. package/lib/shortestPath.ts +122 -0
  104. package/lib/typeTests/DBoGenerated.d.ts +320 -0
  105. package/lib/typeTests/dboTypeCheck.ts +81 -0
  106. package/lib/utils.ts +15 -0
  107. package/package.json +1 -1
@@ -0,0 +1,126 @@
1
+ import { AnyObject, UpdateParams } from "prostgles-types";
2
+ import { TableRule } from "../../PublishParser/PublishParser";
3
+ import { Filter, LocalParams, getErrorAsObject, getSerializedClientErrorFromPGError, withUserRLS } from "../DboBuilder";
4
+ import { getInsertTableRules, getReferenceColumnInserts } from "../insertNestedRecords";
5
+ import { prepareNewData } from "./DataValidator";
6
+ import { runInsertUpdateQuery } from "./runInsertUpdateQuery";
7
+ import { TableHandler } from "./TableHandler";
8
+ import { updateFile } from "./updateFile";
9
+
10
+ export async function update(this: TableHandler, filter: Filter, _newData: AnyObject, params?: UpdateParams, tableRules?: TableRule, localParams?: LocalParams): Promise<AnyObject | void> {
11
+ const ACTION = "update";
12
+ const start = Date.now();
13
+ try {
14
+ /** postValidate */
15
+ const finalDBtx = this.getFinalDBtx(localParams);
16
+ const wrapInTx = () => this.dboBuilder.getTX(_dbtx => _dbtx[this.name]?.[ACTION]?.(filter, _newData, params, tableRules, localParams))
17
+ const rule = tableRules?.[ACTION]
18
+ if(rule?.postValidate && !finalDBtx){
19
+ return wrapInTx();
20
+ }
21
+
22
+ let newData = _newData;
23
+ if(this.is_media){
24
+ ({ newData } = await updateFile.bind(this)({ newData, filter, localParams, tableRules }));
25
+ }
26
+
27
+ const parsedRules = await this.parseUpdateRules(filter, params, tableRules, localParams)
28
+ if (localParams?.testRule) {
29
+ return parsedRules;
30
+ }
31
+
32
+ if (!newData || !Object.keys(newData).length) {
33
+ throw "no update data provided\nEXPECTING db.table.update(filter, updateData, options)";
34
+ }
35
+
36
+ const { fields, validateRow, forcedData, returningFields, forcedFilter, filterFields } = parsedRules;
37
+ const { onConflict, fixIssues = false } = params || {};
38
+ const { returnQuery = false } = localParams ?? {};
39
+
40
+ if (params) {
41
+ const good_paramsObj: Record<keyof UpdateParams, 1> = { returning: 1, returnType: 1, fixIssues: 1, onConflict: 1, multi: 1 };
42
+ const good_params = Object.keys(good_paramsObj);
43
+ const bad_params = Object.keys(params).filter(k => !good_params.includes(k));
44
+ if (bad_params && bad_params.length) throw "Invalid params: " + bad_params.join(", ") + " \n Expecting: " + good_params.join(", ");
45
+ }
46
+
47
+ const { data, allowedCols } = await prepareNewData({
48
+ row: newData,
49
+ forcedData,
50
+ allowedFields: fields,
51
+ tableRules,
52
+ fixIssues,
53
+ tableConfigurator: this.dboBuilder.prostgles.tableConfigurator,
54
+ tableHandler: this,
55
+ });
56
+
57
+ const updateFilter = await this.prepareWhere({
58
+ select: undefined,
59
+ filter,
60
+ forcedFilter,
61
+ filterFields,
62
+ localParams,
63
+ tableRule: tableRules
64
+ })
65
+
66
+ /**
67
+ * Nested inserts
68
+ */
69
+ const nData = { ...data };
70
+ const nestedInserts = getReferenceColumnInserts(this, nData, true);
71
+ const nestedInsertsResultsObj: Record<string, any> = {};
72
+ if(nestedInserts.length){
73
+ const updateCount = await this.count(updateFilter.filter);
74
+ if(+updateCount > 1){
75
+ throw "Cannot do a nestedInsert from an update that targets more than 1 row";
76
+ }
77
+ if(!finalDBtx){
78
+ return wrapInTx();
79
+ }
80
+ await Promise.all(nestedInserts.map(async nestedInsert => {
81
+ const nesedTableHandler = finalDBtx[nestedInsert.tableName] as TableHandler | undefined;
82
+ if(!nesedTableHandler) throw `nestedInsert Tablehandler not found for ${nestedInsert.tableName}`;
83
+ const refTableRules = !localParams? undefined : await getInsertTableRules(this, nestedInsert.tableName, localParams);
84
+ const nestedLocalParams: LocalParams = { ...localParams, nestedInsert: { depth: 1, previousData: nData, previousTable: this.name, referencingColumn: nestedInsert.col } }
85
+ const nestedInsertResult = await nesedTableHandler.insert(nestedInsert.data, { returning: "*" }, undefined, refTableRules, nestedLocalParams);
86
+ nestedInsertsResultsObj[nestedInsert.col] = nestedInsertResult;
87
+
88
+ nData[nestedInsert.col] = nestedInsertResult[nestedInsert.fcol];
89
+ return {
90
+ ...nestedInsert,
91
+ result: nestedInsertResult,
92
+ }
93
+ }));
94
+ }
95
+
96
+ // let query = await this.colSet.getUpdateQuery(nData, allowedCols, this.getFinalDbo(localParams), validateRow, localParams)
97
+ let query = (await this.dataValidator.parse({ command: "update", rows: [nData], allowedCols, dbTx: this.getFinalDbo(localParams), validationOptions: { validate: validateRow, localParams }})).getQuery()
98
+ query += "\n" + updateFilter.where;
99
+ if (onConflict === "DoNothing") query += " ON CONFLICT DO NOTHING ";
100
+ if(onConflict === "DoUpdate"){
101
+ throw "onConflict 'DoUpdate' not possible for an update";
102
+ }
103
+ const queryWithoutUserRLS = query;
104
+ query = withUserRLS(localParams, query);
105
+
106
+ if (returnQuery) return query as unknown as void;
107
+
108
+ const result = await runInsertUpdateQuery({
109
+ tableHandler: this,
110
+ data: undefined,
111
+ fields,
112
+ localParams,
113
+ params,
114
+ queryWithoutUserRLS,
115
+ returningFields,
116
+ rule,
117
+ type: "update",
118
+ nestedInsertsResultsObj
119
+ });
120
+ await this._log({ command: "update", localParams, data: { filter, _newData, params }, duration: Date.now() - start });
121
+ return result;
122
+ } catch (e) {
123
+ await this._log({ command: "update", localParams, data: { filter, _newData, params }, duration: Date.now() - start, error: getErrorAsObject(e) });
124
+ throw getSerializedClientErrorFromPGError(e, { type: "tableMethod", localParams, view: this });
125
+ }
126
+ }
@@ -0,0 +1,49 @@
1
+ import { AnyObject, UpdateParams } from "prostgles-types";
2
+ import { TableRule } from "../../PublishParser/PublishParser";
3
+ import { Filter, LocalParams, getClientErrorFromPGError, getErrorAsObject, getSerializedClientErrorFromPGError, withUserRLS } from "../DboBuilder";
4
+ import { TableHandler } from "./TableHandler";
5
+
6
+
7
+ export async function updateBatch(this: TableHandler, updates: [Filter, AnyObject][], params?: UpdateParams, tableRules?: TableRule, localParams?: LocalParams): Promise<any> {
8
+ const start = Date.now();
9
+ try {
10
+ const { checkFilter, postValidate } = tableRules?.update ?? {};
11
+ if(checkFilter || postValidate){
12
+ throw `updateBatch not allowed for tables with checkFilter or postValidate rules`
13
+ }
14
+ const updateQueries: string[] = await Promise.all(
15
+ updates.map(async ([filter, data]) => {
16
+ const query = (await this.update(
17
+ filter,
18
+ data,
19
+ { ...(params ?? {}), returning: undefined },
20
+ tableRules,
21
+ { ...(localParams ?? {}), returnQuery: "noRLS" }
22
+ )) as unknown as string;
23
+
24
+ return query;
25
+ })
26
+ );
27
+ const queries = [
28
+ withUserRLS(localParams, ""),
29
+ ...updateQueries
30
+ ];
31
+
32
+ const t = localParams?.tx?.t ?? this.tx?.t;
33
+ if(t){
34
+ const result = await t.none(queries.join(";\n"));
35
+ await this._log({ command: "updateBatch", localParams, data: { data: updates, params }, duration: Date.now() - start });
36
+ return result;
37
+ }
38
+ const result = await this.db.tx(t => {
39
+ return t.none(queries.join(";\n"));
40
+ })
41
+ .catch(err => getClientErrorFromPGError(err, { type: "tableMethod", localParams, view: this, allowedKeys: []}));
42
+
43
+ await this._log({ command: "updateBatch", localParams, data: { data: updates, params }, duration: Date.now() - start });
44
+ return result;
45
+ } catch (e) {
46
+ await this._log({ command: "updateBatch", localParams, data: { data: updates, params }, duration: Date.now() - start, error: getErrorAsObject(e) });
47
+ throw getSerializedClientErrorFromPGError(e, { type: "tableMethod", localParams, view: this });
48
+ }
49
+ }
@@ -0,0 +1,48 @@
1
+ import { AnyObject, getKeys, isObject } from "prostgles-types";
2
+ import { LocalParams, Media } from "../DboBuilder";
3
+ import { TableRule, ValidateRow, ValidateRowBasic } from "../../PublishParser/PublishParser";
4
+ import { omitKeys } from "../../PubSubManager/PubSubManager";
5
+ import { isFile, uploadFile } from "../uploadFile";
6
+ import { TableHandler } from "./TableHandler";
7
+ import { DBOFullyTyped } from "../../DBSchemaBuilder";
8
+
9
+ type Args = {
10
+ newData: AnyObject;
11
+ filter: AnyObject;
12
+ tableRules: TableRule | undefined;
13
+ localParams: LocalParams | undefined;
14
+ }
15
+ export const updateFile = async function(this: TableHandler, { filter, newData, tableRules, localParams }: Args): Promise<{ newData: AnyObject }> {
16
+
17
+ const rule = tableRules?.update;
18
+
19
+ if(tableRules && !tableRules.update){
20
+ throw "Not allowed"
21
+ }
22
+ if(localParams?.testRule){
23
+ return { newData: {} };
24
+ }
25
+ const existingMediaId: string = !(!filter || !isObject(filter) || getKeys(filter).join() !== "id" || typeof (filter as any).id !== "string")? (filter as any).id : undefined
26
+ if(!existingMediaId){
27
+ throw new Error(`Updating the file table with file data can only be done by providing a single id filter. E.g. { id: "9ea4e23c-2b1a-4e33-8ec0-c15919bb45ec" } `);
28
+ }
29
+ if(!isFile(newData)){
30
+ throw new Error("Expecting { data: Buffer, name: string } but received " + JSON.stringify(newData))
31
+ }
32
+
33
+ const fileManager = this.dboBuilder.prostgles.fileManager
34
+ if(!fileManager) throw new Error("fileManager missing");
35
+ if(rule?.validate && !localParams) throw new Error("localParams missing");
36
+ const validate: ValidateRowBasic | undefined = rule?.validate? async (row) => {
37
+ return rule.validate!({ update: row, filter, dbx: (this.tx?.dbTX || this.dboBuilder.dbo) as any, localParams: localParams! })
38
+ } : undefined;
39
+
40
+ const existingFile: Media | undefined = await (localParams?.tx?.dbTX?.[this.name] as TableHandler || this).findOne({ id: existingMediaId });
41
+
42
+ if(!existingFile?.name) throw new Error("Existing file record not found");
43
+
44
+ await fileManager.deleteFile(existingFile.name);
45
+ const newFile = await uploadFile.bind(this)({ row: newData, validate, localParams, mediaId: existingFile.id })
46
+ return { newData: omitKeys(newFile, ["id"]) };
47
+
48
+ }
@@ -0,0 +1,34 @@
1
+ import { AnyObject, UpdateParams } from "prostgles-types";
2
+ import { TableRule } from "../../PublishParser/publishTypesAndUtils";
3
+ import { Filter, LocalParams } from "../DboBuilderTypes";
4
+ import { getErrorAsObject, getSerializedClientErrorFromPGError } from "../dboBuilderUtils";
5
+ import { TableHandler } from "./TableHandler";
6
+
7
+ export const upsert = async function(this: TableHandler, filter: Filter, newData: AnyObject, params?: UpdateParams, table_rules?: TableRule, localParams?: LocalParams): Promise<any> {
8
+ const start = Date.now();
9
+ try {
10
+ const _upsert = async function (tblH: TableHandler) {
11
+ return tblH.find(filter, { select: "", limit: 1 }, undefined, table_rules, localParams)
12
+ .then(exists => {
13
+ if (exists && exists.length) {
14
+ return tblH.update(filter, newData, params, table_rules, localParams);
15
+ } else {
16
+ return tblH.insert({ ...newData, ...filter }, params, undefined, table_rules, localParams);
17
+ }
18
+ });
19
+ }
20
+
21
+ /* Do it within a transaction to ensure consisency */
22
+ if (!this.tx) {
23
+ return this.dboBuilder.getTX(dbTX => _upsert(dbTX[this.name] as TableHandler))
24
+ } else {
25
+ const result = await _upsert(this);
26
+ await this._log({ command: "upsert", localParams, data: { filter, newData, params }, duration: Date.now() - start });
27
+ return result;
28
+ }
29
+
30
+ } catch (e) {
31
+ await this._log({ command: "upsert", localParams, data: { filter, newData, params }, duration: Date.now() - start, error: getErrorAsObject(e) });
32
+ throw getSerializedClientErrorFromPGError(e, { type: "tableMethod", localParams, view: this });
33
+ }
34
+ }
@@ -0,0 +1,393 @@
1
+ import * as pgPromise from 'pg-promise';
2
+ import {
3
+ AnyObject,
4
+ ColumnInfo, FieldFilter, SelectParams,
5
+ SubscribeParams,
6
+ asName,
7
+ isEmpty,
8
+ isObject
9
+ } from "prostgles-types";
10
+ import { TableEvent } from "../../Logging";
11
+ import { DB } from "../../Prostgles";
12
+ import { Join } from "../../ProstglesTypes";
13
+ import { TableRule } from "../../PublishParser/PublishParser";
14
+ import { Graph } from "../../shortestPath";
15
+ import {
16
+ DboBuilder,
17
+ Filter,
18
+ LocalParams,
19
+ TableHandlers, ValidatedTableRules,
20
+ escapeTSNames,
21
+ getSerializedClientErrorFromPGError,
22
+ postgresToTsType
23
+ } from "../DboBuilder";
24
+ import { TableSchema } from '../DboBuilderTypes';
25
+ import { COMPUTED_FIELDS, FieldSpec } from "../QueryBuilder/Functions";
26
+ import { asNameAlias } from "../QueryBuilder/QueryBuilder";
27
+ import { getColumns } from "../getColumns";
28
+ import { count } from "./count";
29
+ import { find } from "./find";
30
+ import { getInfo } from "./getInfo";
31
+ import { parseFieldFilter } from "./parseFieldFilter";
32
+ import { prepareWhere } from "./prepareWhere";
33
+ import { size } from "./size";
34
+ import { LocalFuncs, subscribe } from "./subscribe";
35
+ import { validateViewRules } from "./validateViewRules";
36
+
37
+ export type JoinPaths = {
38
+ t1: string;
39
+ t2: string;
40
+ path: string[];
41
+ }[];
42
+
43
+ export class ViewHandler {
44
+ db: DB;
45
+ name: string;
46
+ escapedName: string;
47
+ columns: TableSchema["columns"];
48
+ columnsForTypes: ColumnInfo[];
49
+ column_names: string[];
50
+ tableOrViewInfo: TableSchema;
51
+ tsColumnDefs: string[] = [];
52
+ joins: Join[];
53
+ joinGraph?: Graph;
54
+ joinPaths?: JoinPaths;
55
+ dboBuilder: DboBuilder;
56
+
57
+ tx?: {
58
+ t: pgPromise.ITask<{}>;
59
+ dbTX: TableHandlers;
60
+ }
61
+ get dbHandler() {
62
+ return this.tx?.t ?? this.db;
63
+ }
64
+
65
+ is_view = true;
66
+ filterDef = "";
67
+ is_media = false;
68
+ constructor(db: DB, tableOrViewInfo: TableSchema, dboBuilder: DboBuilder, tx?: { t: pgPromise.ITask<{}>, dbTX: TableHandlers }, joinPaths?: JoinPaths) {
69
+ if (!db || !tableOrViewInfo) throw "";
70
+
71
+ this.db = db;
72
+ this.tx = tx;
73
+ this.joinPaths = joinPaths;
74
+ this.tableOrViewInfo = tableOrViewInfo;
75
+ this.name = tableOrViewInfo.escaped_identifier;
76
+ this.escapedName = tableOrViewInfo.escaped_identifier;
77
+ this.columns = tableOrViewInfo.columns;
78
+ /* cols are sorted by name to reduce .d.ts schema rewrites */
79
+ this.columnsForTypes = tableOrViewInfo.columns.slice(0).sort((a, b) => a.name.localeCompare(b.name));
80
+
81
+ this.column_names = tableOrViewInfo.columns.map(c => c.name);
82
+
83
+ this.dboBuilder = dboBuilder;
84
+ this.joins = this.dboBuilder.joins ?? [];
85
+ this.columnsForTypes.map(({ name, udt_name, is_nullable }) => {
86
+ this.tsColumnDefs.push(`${escapeTSNames(name)}?: ${postgresToTsType(udt_name) as string} ${is_nullable ? " | null " : ""};`);
87
+ });
88
+ }
89
+
90
+ _log = ({ command, data, localParams, duration, error }: Pick<TableEvent, "command" | "data" | "localParams"> & { duration: number; error?: any; }) => {
91
+ if(localParams?.noLog){
92
+ if(localParams?.socket || localParams.httpReq) {
93
+ throw new Error("noLog option is not allowed from a remote client");
94
+ }
95
+ return;
96
+ }
97
+ const sid = this.dboBuilder.prostgles.authHandler?.getSIDNoError(localParams);
98
+ return this.dboBuilder.prostgles.opts.onLog?.({
99
+ type: "table",
100
+ command,
101
+ duration,
102
+ error,
103
+ txInfo: this.tx?.t.ctx,
104
+ sid,
105
+ socketId: localParams?.socket?.id,
106
+ tableName: this.name,
107
+ data,
108
+ localParams,
109
+ });
110
+ }
111
+
112
+ getRowHashSelect(allowedFields: FieldFilter, alias?: string, tableAlias?: string): string {
113
+ let allowed_cols = this.column_names;
114
+ if (allowedFields) allowed_cols = this.parseFieldFilter(allowedFields);
115
+ return "md5(" +
116
+ allowed_cols
117
+ /* CTID not available in AFTER trigger */
118
+ // .concat(this.is_view? [] : ["ctid"])
119
+ .sort()
120
+ .map(f => (tableAlias ? (asName(tableAlias) + ".") : "") + asName(f))
121
+ .map(f => `md5(coalesce(${f}::text, 'dd'))`)
122
+ .join(" || ") +
123
+ `)` + (alias ? ` as ${asName(alias)}` : "");
124
+ }
125
+
126
+ validateViewRules = validateViewRules.bind(this);
127
+
128
+ getShortestJoin(table1: string, table2: string, startAlias: number, isInner = false): { query: string, toOne: boolean } {
129
+ const getJoinCondition = (on: Record<string, string>[], leftTable: string, rightTable: string) => {
130
+ return on.map(cond => Object.keys(cond).map(lKey => `${leftTable}.${lKey} = ${rightTable}.${cond[lKey]}`).join("\nAND ")).join(" OR ")
131
+ }
132
+
133
+ // let toOne = true;
134
+ const query = this.joins.map(({ tables, on, type }, i) => {
135
+ if (type.split("-")[1] === "many") {
136
+ // toOne = false;
137
+ }
138
+ const tl = `tl${startAlias + i}`,
139
+ tr = `tr${startAlias + i}`;
140
+ return `FROM ${tables[0]} ${tl} ${isInner ? "INNER" : "LEFT"} JOIN ${tables[1]} ${tr} ON ${getJoinCondition(on, tl, tr)}`;
141
+ }).join("\n");
142
+ return { query, toOne: false }
143
+ }
144
+
145
+ checkFilter(filter: any) {
146
+ if (filter === null || filter && !isObject(filter)) throw `invalid filter -> ${JSON.stringify(filter)} \nExpecting: undefined | {} | { field_name: "value" } | { field: { $gt: 22 } } ... `;
147
+ }
148
+
149
+ getInfo = getInfo.bind(this)
150
+
151
+ getColumns = getColumns.bind(this);
152
+
153
+ getValidatedRules(tableRules?: TableRule, localParams?: LocalParams): ValidatedTableRules {
154
+
155
+ if (localParams?.socket && !tableRules) {
156
+ throw "INTERNAL ERROR: Unexpected case -> localParams && !tableRules";
157
+ }
158
+
159
+ /* Computed fields are allowed only if select is allowed */
160
+ const allColumns: FieldSpec[] = this.column_names.slice(0).map(fieldName => ({
161
+ type: "column",
162
+ name: fieldName,
163
+ getQuery: ({ tableAlias }) => asNameAlias(fieldName, tableAlias),
164
+ selected: false
165
+ } as FieldSpec)).concat(COMPUTED_FIELDS.map(c => ({
166
+ type: c.type,
167
+ name: c.name,
168
+ getQuery: ({ tableAlias, allowedFields }) => c.getQuery({
169
+ allowedFields,
170
+ ctidField: undefined,
171
+ allColumns: this.columns,
172
+
173
+ /* CTID not available in AFTER trigger */
174
+ // ctidField: this.is_view? undefined : "ctid",
175
+ tableAlias
176
+ }),
177
+ selected: false
178
+ })));
179
+
180
+ if (tableRules) {
181
+ if (isEmpty(tableRules)) throw "INTERNAL ERROR: Unexpected case -> Empty table rules for " + this.name;
182
+ const throwFieldsErr = (command: "select" | "update" | "delete" | "insert", fieldType = "fields") => {
183
+ throw `Invalid publish.${this.name}.${command} rule -> ${fieldType} setting is missing.\nPlease specify allowed ${fieldType} in this format: "*" | { col_name: false } | { col1: true, col2: true }`;
184
+ },
185
+ getFirstSpecified = (...fieldParams: (FieldFilter | undefined)[]): string[] => {
186
+ const firstValid = fieldParams.find(fp => fp !== undefined);
187
+ return this.parseFieldFilter(firstValid)
188
+ };
189
+
190
+ const res: ValidatedTableRules = {
191
+ allColumns,
192
+ getColumns: tableRules?.getColumns ?? true,
193
+ getInfo: tableRules?.getColumns ?? true,
194
+ } as ValidatedTableRules;
195
+
196
+ if (tableRules.select) {
197
+ if (!tableRules.select.fields) return throwFieldsErr("select");
198
+
199
+ let maxLimit: number | null = null;
200
+ if (!localParams?.bypassLimit && tableRules.select.maxLimit !== undefined && tableRules.select.maxLimit !== maxLimit) {
201
+ const ml = tableRules.select.maxLimit;
202
+ if (ml !== null && (!Number.isInteger(ml) || ml < 0)) throw ` Invalid publish.${this.name}.select.maxLimit -> expecting a positive integer OR null but got ` + ml;
203
+ maxLimit = ml;
204
+ }
205
+
206
+ const fields = this.parseFieldFilter(tableRules.select.fields)
207
+ res.select = {
208
+ fields,
209
+ orderByFields: tableRules.select.orderByFields ? this.parseFieldFilter(tableRules.select.orderByFields) : fields,
210
+ forcedFilter: { ...tableRules.select.forcedFilter },
211
+ filterFields: this.parseFieldFilter(tableRules.select.filterFields),
212
+ maxLimit
213
+ };
214
+ }
215
+
216
+ if (tableRules.update) {
217
+ if (!tableRules.update.fields) return throwFieldsErr("update");
218
+
219
+ res.update = {
220
+ fields: this.parseFieldFilter(tableRules.update.fields),
221
+ forcedData: { ...tableRules.update.forcedData },
222
+ forcedFilter: { ...tableRules.update.forcedFilter },
223
+ returningFields: getFirstSpecified(tableRules.update?.returningFields, tableRules?.select?.fields, tableRules.update.fields),
224
+ filterFields: this.parseFieldFilter(tableRules.update.filterFields)
225
+ }
226
+ }
227
+
228
+ if (tableRules.insert) {
229
+ if (!tableRules.insert.fields) return throwFieldsErr("insert");
230
+
231
+ res.insert = {
232
+ fields: this.parseFieldFilter(tableRules.insert.fields),
233
+ forcedData: { ...tableRules.insert.forcedData },
234
+ returningFields: getFirstSpecified(tableRules.insert.returningFields, tableRules?.select?.fields, tableRules.insert.fields)
235
+ }
236
+ }
237
+
238
+ if (tableRules.delete) {
239
+ if (!tableRules.delete.filterFields) return throwFieldsErr("delete", "filterFields");
240
+
241
+ res.delete = {
242
+ forcedFilter: { ...tableRules.delete.forcedFilter },
243
+ filterFields: this.parseFieldFilter(tableRules.delete.filterFields),
244
+ returningFields: getFirstSpecified(tableRules.delete.returningFields, tableRules?.select?.fields, tableRules.delete.filterFields)
245
+ }
246
+ }
247
+
248
+ if (!tableRules.select && !tableRules.update && !tableRules.delete && !tableRules.insert) {
249
+ if ([null, false].includes(tableRules.getInfo as any)) res.getInfo = false;
250
+ if ([null, false].includes(tableRules.getColumns as any)) res.getColumns = false;
251
+ }
252
+
253
+ return res;
254
+ } else {
255
+ const allCols = this.column_names.slice(0);
256
+ return {
257
+ allColumns,
258
+ getColumns: true,
259
+ getInfo: true,
260
+ select: {
261
+ fields: allCols,
262
+ filterFields: allCols,
263
+ orderByFields: allCols,
264
+ forcedFilter: {},
265
+ maxLimit: null,
266
+ },
267
+ update: {
268
+ fields: allCols,
269
+ filterFields: allCols,
270
+ forcedFilter: {},
271
+ forcedData: {},
272
+ returningFields: allCols
273
+ },
274
+ insert: {
275
+ fields: allCols,
276
+ forcedData: {},
277
+ returningFields: allCols
278
+ },
279
+ delete: {
280
+ filterFields: allCols,
281
+ forcedFilter: {},
282
+ returningFields: allCols
283
+ }
284
+ };
285
+
286
+ }
287
+
288
+ }
289
+ find = find.bind(this);
290
+
291
+ async findOne(filter?: Filter, selectParams?: SelectParams, _param3_unused?: undefined, table_rules?: TableRule, localParams?: LocalParams): Promise<any> {
292
+
293
+ try {
294
+ const { limit, ...params } = selectParams ?? {};
295
+ if (limit) {
296
+ throw "limit not allowed in findOne()";
297
+ }
298
+ const start = Date.now();
299
+ const result = await this.find(filter, { ...params, limit: 1, returnType: "row" }, undefined, table_rules, localParams);
300
+ await this._log({ command: "find", localParams, data: { filter, selectParams }, duration: Date.now() - start });
301
+ return result;
302
+ } catch (e) {
303
+ throw getSerializedClientErrorFromPGError(e, { type: "tableMethod", localParams, view: this });
304
+ }
305
+ }
306
+
307
+ async subscribe(filter: Filter, params: SubscribeParams, localFuncs: LocalFuncs): Promise<{ unsubscribe: () => any }>
308
+ async subscribe(filter: Filter, params: SubscribeParams, localFuncs: undefined, table_rules: TableRule | undefined, localParams: LocalParams): Promise<string>
309
+ async subscribe(filter: Filter, params: SubscribeParams, localFuncs?: LocalFuncs, table_rules?: TableRule, localParams?: LocalParams):
310
+ Promise<{ unsubscribe: () => any } | string> {
311
+ //@ts-ignore
312
+ return subscribe.bind(this)(filter, params, localFuncs, table_rules, localParams);
313
+ }
314
+
315
+ /* This should only be called from server */
316
+ subscribeOne(filter: Filter, params: SubscribeParams, localFunc: (item: AnyObject) => any): Promise<{ unsubscribe: () => any }>
317
+ subscribeOne(filter: Filter, params: SubscribeParams, localFunc: undefined, table_rules: TableRule, localParams: LocalParams): Promise<string>
318
+ subscribeOne(filter: Filter, params: SubscribeParams = {}, localFunc?: (item: AnyObject) => any, table_rules?: TableRule, localParams?: LocalParams):
319
+ Promise<string | { unsubscribe: () => any }> {
320
+
321
+ //@ts-ignore
322
+ const func = localParams? undefined : (rows: AnyObject[]) => localFunc(rows[0]);
323
+ //@ts-ignore
324
+ return this.subscribe(filter, { ...params, limit: 2 }, func, table_rules, localParams);
325
+ }
326
+
327
+ count = count.bind(this);
328
+ size = size.bind(this);
329
+
330
+ getAllowedSelectFields(selectParams: FieldFilter = "*", allowed_cols: FieldFilter, allow_empty = true): string[] {
331
+ const all_columns = this.column_names.slice(0);
332
+ let allowedFields = all_columns.slice(0),
333
+ resultFields: string[] = [];
334
+
335
+ if (selectParams) {
336
+ resultFields = this.parseFieldFilter(selectParams, allow_empty);
337
+ }
338
+ if (allowed_cols) {
339
+ allowedFields = this.parseFieldFilter(allowed_cols, allow_empty);
340
+ }
341
+ let col_names = (resultFields || []).filter(f => !allowedFields || allowedFields.includes(f));
342
+
343
+ /* Maintain allowed cols order */
344
+ if (selectParams === "*" && allowedFields && allowedFields.length){
345
+ col_names = allowedFields;
346
+ }
347
+
348
+ return col_names;
349
+ }
350
+
351
+ /**
352
+ * Parses group or simple filter
353
+ */
354
+ prepareWhere = prepareWhere.bind(this);
355
+
356
+ intersectColumns(allowedFields: FieldFilter, dissallowedFields: FieldFilter, fixIssues = false): string[] {
357
+ let result: string[] = [];
358
+ if (allowedFields) {
359
+ result = this.parseFieldFilter(allowedFields);
360
+ }
361
+ if (dissallowedFields) {
362
+ const _dissalowed = this.parseFieldFilter(dissallowedFields);
363
+
364
+ if (!fixIssues) {
365
+
366
+ throw `dissallowed/invalid field found for ${this.name}: `
367
+ }
368
+ result = result.filter(key => !_dissalowed.includes(key));
369
+ }
370
+
371
+ return result;
372
+ }
373
+
374
+ parseFieldFilter(fieldParams: FieldFilter = "*", allow_empty = true, allowed_cols?: string[]): string[] {
375
+ return parseFieldFilter(fieldParams, allow_empty, allowed_cols ?? this.column_names.slice(0))
376
+ }
377
+
378
+ }
379
+
380
+
381
+ /**
382
+ * Throw error if illegal keys found in object
383
+ */
384
+ export const validateObj = <T extends Record<string, any>>(obj: T, allowedKeys: string[]): T => {
385
+ if (obj && Object.keys(obj).length) {
386
+ const invalid_keys = Object.keys(obj).filter(k => !allowedKeys.includes(k));
387
+ if (invalid_keys.length) {
388
+ throw "Invalid/Illegal fields found: " + invalid_keys.join(", ");
389
+ }
390
+ }
391
+
392
+ return obj;
393
+ }
@@ -0,0 +1,38 @@
1
+ import { SelectParams } from "prostgles-types";
2
+ import { TableRule } from "../../PublishParser/publishTypesAndUtils";
3
+ import { Filter, LocalParams } from "../DboBuilder";
4
+ import { getErrorAsObject, getSerializedClientErrorFromPGError, withUserRLS } from "../dboBuilderUtils";
5
+ import { ViewHandler } from "./ViewHandler";
6
+
7
+ export async function count(this: ViewHandler, _filter?: Filter, selectParams?: SelectParams, _param3_unused?: undefined, table_rules?: TableRule, localParams?: LocalParams): Promise<number> {
8
+ const filter = _filter || {};
9
+ const { limit: _limit, ...selectParamsWithoutLimit } = selectParams ?? {};
10
+ const start = Date.now();
11
+ try {
12
+ const result = await this.find(filter, { select: selectParamsWithoutLimit?.select ?? "", limit: 0 }, undefined, table_rules, localParams)
13
+ .then(async _allowed => {
14
+ const findQuery = await this.find(
15
+ filter,
16
+ selectParamsWithoutLimit,
17
+ undefined,
18
+ table_rules,
19
+ { ...localParams, returnQuery: "noRLS", bypassLimit: true }
20
+ ) as unknown as string;
21
+ const query = [
22
+ withUserRLS(localParams, ""),
23
+ "SELECT COUNT(*)",
24
+ "FROM (",
25
+ findQuery,
26
+ ") t"
27
+ ].join("\n");
28
+ const handler = this.tx?.t ?? this.db;
29
+ return handler.one(query).then(({ count }) => +count);
30
+ });
31
+
32
+ await this._log({ command: "count", localParams, data: { filter }, duration: Date.now() - start });
33
+ return result;
34
+ } catch (e) {
35
+ await this._log({ command: "count", localParams, data: { filter }, duration: Date.now() - start, error: getErrorAsObject(e) });
36
+ throw getSerializedClientErrorFromPGError(e, { type: "tableMethod", localParams, view: this });
37
+ }
38
+ }