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.
- package/dist/Auth/setEmailProvider.js +2 -2
- package/dist/Auth/setEmailProvider.js.map +1 -1
- package/lib/Auth/AuthHandler.ts +436 -0
- package/lib/Auth/AuthTypes.ts +280 -0
- package/lib/Auth/getSafeReturnURL.ts +35 -0
- package/lib/Auth/sendEmail.ts +83 -0
- package/lib/Auth/setAuthProviders.ts +128 -0
- package/lib/Auth/setEmailProvider.ts +85 -0
- package/lib/Auth/setupAuthRoutes.ts +161 -0
- package/lib/DBEventsManager.ts +178 -0
- package/lib/DBSchemaBuilder.ts +225 -0
- package/lib/DboBuilder/DboBuilder.ts +319 -0
- package/lib/DboBuilder/DboBuilderTypes.ts +361 -0
- package/lib/DboBuilder/QueryBuilder/Functions.ts +1153 -0
- package/lib/DboBuilder/QueryBuilder/QueryBuilder.ts +288 -0
- package/lib/DboBuilder/QueryBuilder/getJoinQuery.ts +263 -0
- package/lib/DboBuilder/QueryBuilder/getNewQuery.ts +271 -0
- package/lib/DboBuilder/QueryBuilder/getSelectQuery.ts +136 -0
- package/lib/DboBuilder/QueryBuilder/prepareHaving.ts +22 -0
- package/lib/DboBuilder/QueryStreamer.ts +250 -0
- package/lib/DboBuilder/TableHandler/DataValidator.ts +428 -0
- package/lib/DboBuilder/TableHandler/TableHandler.ts +205 -0
- package/lib/DboBuilder/TableHandler/delete.ts +115 -0
- package/lib/DboBuilder/TableHandler/insert.ts +183 -0
- package/lib/DboBuilder/TableHandler/insertTest.ts +78 -0
- package/lib/DboBuilder/TableHandler/onDeleteFromFileTable.ts +62 -0
- package/lib/DboBuilder/TableHandler/runInsertUpdateQuery.ts +134 -0
- package/lib/DboBuilder/TableHandler/update.ts +126 -0
- package/lib/DboBuilder/TableHandler/updateBatch.ts +49 -0
- package/lib/DboBuilder/TableHandler/updateFile.ts +48 -0
- package/lib/DboBuilder/TableHandler/upsert.ts +34 -0
- package/lib/DboBuilder/ViewHandler/ViewHandler.ts +393 -0
- package/lib/DboBuilder/ViewHandler/count.ts +38 -0
- package/lib/DboBuilder/ViewHandler/find.ts +153 -0
- package/lib/DboBuilder/ViewHandler/getExistsCondition.ts +73 -0
- package/lib/DboBuilder/ViewHandler/getExistsFilters.ts +74 -0
- package/lib/DboBuilder/ViewHandler/getInfo.ts +32 -0
- package/lib/DboBuilder/ViewHandler/getTableJoinQuery.ts +84 -0
- package/lib/DboBuilder/ViewHandler/parseComplexFilter.ts +96 -0
- package/lib/DboBuilder/ViewHandler/parseFieldFilter.ts +105 -0
- package/lib/DboBuilder/ViewHandler/parseJoinPath.ts +208 -0
- package/lib/DboBuilder/ViewHandler/prepareSortItems.ts +163 -0
- package/lib/DboBuilder/ViewHandler/prepareWhere.ts +90 -0
- package/lib/DboBuilder/ViewHandler/size.ts +37 -0
- package/lib/DboBuilder/ViewHandler/subscribe.ts +118 -0
- package/lib/DboBuilder/ViewHandler/validateViewRules.ts +70 -0
- package/lib/DboBuilder/dboBuilderUtils.ts +222 -0
- package/lib/DboBuilder/getColumns.ts +114 -0
- package/lib/DboBuilder/getCondition.ts +201 -0
- package/lib/DboBuilder/getSubscribeRelatedTables.ts +190 -0
- package/lib/DboBuilder/getTablesForSchemaPostgresSQL.ts +426 -0
- package/lib/DboBuilder/insertNestedRecords.ts +355 -0
- package/lib/DboBuilder/parseUpdateRules.ts +187 -0
- package/lib/DboBuilder/prepareShortestJoinPaths.ts +186 -0
- package/lib/DboBuilder/runSQL.ts +182 -0
- package/lib/DboBuilder/runTransaction.ts +50 -0
- package/lib/DboBuilder/sqlErrCodeToMsg.ts +254 -0
- package/lib/DboBuilder/uploadFile.ts +69 -0
- package/lib/Event_Trigger_Tags.ts +118 -0
- package/lib/FileManager/FileManager.ts +358 -0
- package/lib/FileManager/getValidatedFileType.ts +69 -0
- package/lib/FileManager/initFileManager.ts +187 -0
- package/lib/FileManager/upload.ts +62 -0
- package/lib/FileManager/uploadStream.ts +79 -0
- package/lib/Filtering.ts +463 -0
- package/lib/JSONBValidation/validate_jsonb_schema_sql.ts +502 -0
- package/lib/JSONBValidation/validation.ts +143 -0
- package/lib/Logging.ts +127 -0
- package/lib/PostgresNotifListenManager.ts +143 -0
- package/lib/Prostgles.ts +485 -0
- package/lib/ProstglesTypes.ts +196 -0
- package/lib/PubSubManager/PubSubManager.ts +609 -0
- package/lib/PubSubManager/addSub.ts +138 -0
- package/lib/PubSubManager/addSync.ts +141 -0
- package/lib/PubSubManager/getCreatePubSubManagerError.ts +72 -0
- package/lib/PubSubManager/getPubSubManagerInitQuery.ts +662 -0
- package/lib/PubSubManager/initPubSubManager.ts +79 -0
- package/lib/PubSubManager/notifListener.ts +173 -0
- package/lib/PubSubManager/orphanTriggerCheck.ts +70 -0
- package/lib/PubSubManager/pushSubData.ts +55 -0
- package/lib/PublishParser/PublishParser.ts +162 -0
- package/lib/PublishParser/getFileTableRules.ts +124 -0
- package/lib/PublishParser/getSchemaFromPublish.ts +141 -0
- package/lib/PublishParser/getTableRulesWithoutFileTable.ts +177 -0
- package/lib/PublishParser/publishTypesAndUtils.ts +399 -0
- package/lib/RestApi.ts +127 -0
- package/lib/SchemaWatch/SchemaWatch.ts +90 -0
- package/lib/SchemaWatch/createSchemaWatchEventTrigger.ts +3 -0
- package/lib/SchemaWatch/getValidatedWatchSchemaType.ts +45 -0
- package/lib/SchemaWatch/getWatchSchemaTagList.ts +27 -0
- package/lib/SyncReplication.ts +557 -0
- package/lib/TableConfig/TableConfig.ts +468 -0
- package/lib/TableConfig/getColumnDefinitionQuery.ts +111 -0
- package/lib/TableConfig/getConstraintDefinitionQueries.ts +95 -0
- package/lib/TableConfig/getFutureTableSchema.ts +64 -0
- package/lib/TableConfig/getPGIndexes.ts +53 -0
- package/lib/TableConfig/getTableColumnQueries.ts +129 -0
- package/lib/TableConfig/initTableConfig.ts +326 -0
- package/lib/index.ts +13 -0
- package/lib/initProstgles.ts +319 -0
- package/lib/onSocketConnected.ts +102 -0
- package/lib/runClientRequest.ts +129 -0
- package/lib/shortestPath.ts +122 -0
- package/lib/typeTests/DBoGenerated.d.ts +320 -0
- package/lib/typeTests/dboTypeCheck.ts +81 -0
- package/lib/utils.ts +15 -0
- package/package.json +1 -1
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
import * as pgPromise from "pg-promise";
|
|
2
|
+
import pg from "pg-promise/typescript/pg-subset";
|
|
3
|
+
import { getKeys, isEmpty } from "prostgles-types";
|
|
4
|
+
import { AuthHandler } from "./Auth/AuthHandler";
|
|
5
|
+
import { DBEventsManager } from "./DBEventsManager";
|
|
6
|
+
import { DBOFullyTyped } from "./DBSchemaBuilder";
|
|
7
|
+
import { DBHandlerServer, Prostgles, getIsSuperUser } from "./Prostgles";
|
|
8
|
+
import { ProstglesInitOptions } from "./ProstglesTypes";
|
|
9
|
+
import { DbTableInfo, PublishParser } from "./PublishParser/PublishParser";
|
|
10
|
+
import { SchemaWatch } from "./SchemaWatch/SchemaWatch";
|
|
11
|
+
import { sleep } from "./utils";
|
|
12
|
+
|
|
13
|
+
export type DbConnection = string | pg.IConnectionParameters<pg.IClient>;
|
|
14
|
+
export type DbConnectionOpts = pg.IDefaults;
|
|
15
|
+
|
|
16
|
+
export type PGP = pgPromise.IMain<{}, pg.IClient>;
|
|
17
|
+
export type DB = pgPromise.IDatabase<{}, pg.IClient>;
|
|
18
|
+
|
|
19
|
+
export type UpdateableOptions = Pick<ProstglesInitOptions, "fileTable" | "restApi" | "tableConfig" | "schema" | "auth">;
|
|
20
|
+
export type OnInitReason =
|
|
21
|
+
| {
|
|
22
|
+
type: "schema change";
|
|
23
|
+
query: string;
|
|
24
|
+
command: string;
|
|
25
|
+
}
|
|
26
|
+
| {
|
|
27
|
+
type: "prgl.update";
|
|
28
|
+
newOpts: Omit<UpdateableOptions, typeof clientOnlyUpdateKeys[number]>;
|
|
29
|
+
}
|
|
30
|
+
| {
|
|
31
|
+
type: "init" | "prgl.restart" | "TableConfig"
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
type OnReadyParamsCommon = {
|
|
35
|
+
db: DB;
|
|
36
|
+
tables: DbTableInfo[];
|
|
37
|
+
reason: OnInitReason;
|
|
38
|
+
}
|
|
39
|
+
export type OnReadyParamsBasic = OnReadyParamsCommon & {
|
|
40
|
+
dbo: DBHandlerServer;
|
|
41
|
+
}
|
|
42
|
+
export type OnReadyParams<S> = OnReadyParamsCommon & {
|
|
43
|
+
dbo: DBOFullyTyped<S>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export type OnReadyCallback<S = void> = (params: OnReadyParams<S>) => any;
|
|
47
|
+
export type OnReadyCallbackBasic = (params: OnReadyParamsBasic) => any;
|
|
48
|
+
|
|
49
|
+
export type InitResult = {
|
|
50
|
+
db: DBOFullyTyped;
|
|
51
|
+
_db: DB;
|
|
52
|
+
pgp: PGP;
|
|
53
|
+
io?: any;
|
|
54
|
+
destroy: () => Promise<boolean>;
|
|
55
|
+
/**
|
|
56
|
+
* Generated database public schema TS types for all tables and views
|
|
57
|
+
*/
|
|
58
|
+
getTSSchema: () => string;
|
|
59
|
+
update: (newOpts: UpdateableOptions) => Promise<void>;
|
|
60
|
+
restart: () => Promise<InitResult>;
|
|
61
|
+
options: ProstglesInitOptions;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const clientOnlyUpdateKeys = ["auth"] as const satisfies (keyof UpdateableOptions)[];
|
|
65
|
+
|
|
66
|
+
export const initProstgles = async function(this: Prostgles, onReady: OnReadyCallbackBasic, reason: OnInitReason): Promise<InitResult> {
|
|
67
|
+
this.loaded = false;
|
|
68
|
+
|
|
69
|
+
if (!this.db) {
|
|
70
|
+
let existingAppName = "";
|
|
71
|
+
let connString = "";
|
|
72
|
+
if(typeof this.opts.dbConnection === "string"){
|
|
73
|
+
connString = this.opts.dbConnection;
|
|
74
|
+
} else if(this.opts.dbConnection.connectionString){
|
|
75
|
+
connString = this.opts.dbConnection.connectionString;
|
|
76
|
+
} else {
|
|
77
|
+
existingAppName = this.opts.dbConnection.application_name ?? "";
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if(connString){
|
|
81
|
+
try {
|
|
82
|
+
const url = new URL(connString);
|
|
83
|
+
existingAppName = url.searchParams.get("application_name") ?? url.searchParams.get("ApplicationName") ?? "";
|
|
84
|
+
} catch (e) {
|
|
85
|
+
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const conObj = typeof this.opts.dbConnection === "string" ? { connectionString: this.opts.dbConnection } : this.opts.dbConnection
|
|
90
|
+
const application_name = `prostgles ${this.appId} ${existingAppName}`;
|
|
91
|
+
|
|
92
|
+
/* 1. Connect to db */
|
|
93
|
+
const { db, pgp } = getDbConnection({
|
|
94
|
+
...this.opts,
|
|
95
|
+
dbConnection: { ...conObj, application_name },
|
|
96
|
+
onNotice: notice => {
|
|
97
|
+
if (this.opts.onNotice) this.opts.onNotice(notice);
|
|
98
|
+
if (this.dbEventsManager) {
|
|
99
|
+
this.dbEventsManager.onNotice(notice)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
this.db = db;
|
|
104
|
+
this.pgp = pgp;
|
|
105
|
+
this.isSuperUser = await getIsSuperUser(db);
|
|
106
|
+
}
|
|
107
|
+
this.checkDb();
|
|
108
|
+
|
|
109
|
+
const db = this.db!;
|
|
110
|
+
const pgp = this.pgp!;
|
|
111
|
+
|
|
112
|
+
/* 2. Execute any SQL file if provided */
|
|
113
|
+
if (this.opts.sqlFilePath) {
|
|
114
|
+
await this.runSQLFile(this.opts.sqlFilePath);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
try {
|
|
118
|
+
|
|
119
|
+
await this.refreshDBO();
|
|
120
|
+
await this.initTableConfig(reason);
|
|
121
|
+
await this.initFileTable();
|
|
122
|
+
await this.initRestApi();
|
|
123
|
+
|
|
124
|
+
this.schemaWatch = await SchemaWatch.create(this.dboBuilder);
|
|
125
|
+
|
|
126
|
+
if (this.opts.publish) {
|
|
127
|
+
|
|
128
|
+
if (!this.opts.io) {
|
|
129
|
+
console.warn("IO missing. Publish has no effect without io");
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/* 3.9 Check auth config */
|
|
133
|
+
await this.initAuthHandler();
|
|
134
|
+
|
|
135
|
+
this.publishParser = new PublishParser(this.opts.publish, this.opts.publishMethods as any, this.opts.publishRawSQL, this.dbo!, this.db, this as any);
|
|
136
|
+
this.dboBuilder.publishParser = this.publishParser;
|
|
137
|
+
|
|
138
|
+
/* 4. Set publish and auth listeners */
|
|
139
|
+
await this.setSocketEvents();
|
|
140
|
+
|
|
141
|
+
} else if (this.opts.auth) {
|
|
142
|
+
throw "Auth config does not work without publish";
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
this.dbEventsManager = new DBEventsManager(db, pgp);
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
this.writeDBSchema();
|
|
149
|
+
|
|
150
|
+
/* 5. Finish init and provide DBO object */
|
|
151
|
+
try {
|
|
152
|
+
if (this.destroyed) {
|
|
153
|
+
console.trace(1)
|
|
154
|
+
}
|
|
155
|
+
onReady({
|
|
156
|
+
dbo: this.dbo as any,
|
|
157
|
+
db: this.db,
|
|
158
|
+
tables: this.dboBuilder.tables,
|
|
159
|
+
reason
|
|
160
|
+
});
|
|
161
|
+
} catch (err) {
|
|
162
|
+
console.error("Prostgles: Error within onReady: \n", err)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
this.loaded = true;
|
|
166
|
+
return {
|
|
167
|
+
db: this.dbo! as any,
|
|
168
|
+
_db: db,
|
|
169
|
+
pgp,
|
|
170
|
+
io: this.opts.io,
|
|
171
|
+
getTSSchema: this.getTSFileContent,
|
|
172
|
+
options: this.opts,
|
|
173
|
+
update: async (newOpts) => {
|
|
174
|
+
|
|
175
|
+
getKeys(newOpts).forEach(k => {
|
|
176
|
+
//@ts-ignore
|
|
177
|
+
this.opts[k] = newOpts[k];
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
if("fileTable" in newOpts){
|
|
182
|
+
await this.initFileTable();
|
|
183
|
+
}
|
|
184
|
+
if("restApi" in newOpts){
|
|
185
|
+
await this.initRestApi();
|
|
186
|
+
}
|
|
187
|
+
if("tableConfig" in newOpts){
|
|
188
|
+
await this.initTableConfig({ type: "prgl.update", newOpts });
|
|
189
|
+
await this.refreshDBO();
|
|
190
|
+
}
|
|
191
|
+
if("schema" in newOpts){
|
|
192
|
+
await this.initTableConfig({ type: "prgl.update", newOpts });
|
|
193
|
+
await this.refreshDBO();
|
|
194
|
+
}
|
|
195
|
+
if("auth" in newOpts){
|
|
196
|
+
await this.initAuthHandler();
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if(isEmpty(newOpts)) return;
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Some of these changes require clients to reconnect
|
|
203
|
+
* While others also affect the server and onReady should be called
|
|
204
|
+
*/
|
|
205
|
+
if(getKeys(newOpts).every(updatedKey => clientOnlyUpdateKeys.includes(updatedKey as any))){
|
|
206
|
+
await this.setSocketEvents();
|
|
207
|
+
} else {
|
|
208
|
+
await this.init(onReady, { type: "prgl.update", newOpts });
|
|
209
|
+
}
|
|
210
|
+
},
|
|
211
|
+
restart: () => this.init(onReady, { type: "prgl.restart" }),
|
|
212
|
+
destroy: async () => {
|
|
213
|
+
console.log("destroying prgl instance")
|
|
214
|
+
this.destroyed = true;
|
|
215
|
+
if (this.opts.io) {
|
|
216
|
+
this.opts.io.on("connection", () => {
|
|
217
|
+
console.log("Socket connected to destroyed instance")
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
/** Try to close IO without stopping http server */
|
|
221
|
+
if(this.opts.io.sockets.constructor.name === "Namespace"){
|
|
222
|
+
for (const socket of this.opts.io.sockets.sockets.values()) {
|
|
223
|
+
socket._onclose("server shutting down");
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
if(this.opts.io.engine.constructor.name === 'Server'){
|
|
227
|
+
this.opts.io.engine.close();
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
this.fileManager?.destroy();
|
|
231
|
+
this.dboBuilder?.destroy();
|
|
232
|
+
this.authHandler?.destroy();
|
|
233
|
+
await this.tableConfigurator?.destroy();
|
|
234
|
+
this.dbo = undefined;
|
|
235
|
+
this.db = undefined;
|
|
236
|
+
await db.$pool.end();
|
|
237
|
+
await sleep(1000);
|
|
238
|
+
return true;
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
} catch (e: any) {
|
|
242
|
+
console.trace(e)
|
|
243
|
+
throw "init issues: " + e.toString();
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
type GetDbConnectionArgs = Pick<ProstglesInitOptions, "DEBUG_MODE" | "onQuery" | "dbConnection" | "dbOptions" | "onNotice">;
|
|
248
|
+
const getDbConnection = function({ dbConnection, onQuery, DEBUG_MODE, dbOptions, onNotice }: GetDbConnectionArgs): { db: DB, pgp: PGP } {
|
|
249
|
+
|
|
250
|
+
const onQueryOrError: undefined | ((error: any, ctx: pgPromise.IEventContext<pg.IClient>) => void) = !onQuery && !DEBUG_MODE? undefined : (error, ctx) => {
|
|
251
|
+
if (onQuery) {
|
|
252
|
+
onQuery(error, ctx);
|
|
253
|
+
} else if (DEBUG_MODE) {
|
|
254
|
+
if(error){
|
|
255
|
+
console.error(error, ctx);
|
|
256
|
+
} else {
|
|
257
|
+
console.log(ctx)
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
const pgp: PGP = pgPromise({
|
|
263
|
+
...(onQueryOrError ? {
|
|
264
|
+
query: ctx => onQueryOrError(undefined, ctx),
|
|
265
|
+
error: onQueryOrError
|
|
266
|
+
} : {}),
|
|
267
|
+
...((onNotice || DEBUG_MODE) ? {
|
|
268
|
+
connect: function ({ client, useCount }) {
|
|
269
|
+
const isFresh = !useCount;
|
|
270
|
+
if (isFresh && !client.listeners('notice').length) {
|
|
271
|
+
client.on('notice', function (msg) {
|
|
272
|
+
if (onNotice) {
|
|
273
|
+
onNotice(msg, msg?.message);
|
|
274
|
+
} else {
|
|
275
|
+
console.log("notice: %j", msg?.message);
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
if (isFresh && !client.listeners('error').length) {
|
|
280
|
+
client.on('error', function (msg) {
|
|
281
|
+
if (onNotice) {
|
|
282
|
+
onNotice(msg, msg?.message);
|
|
283
|
+
} else {
|
|
284
|
+
console.log("error: %j", msg?.message);
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
},
|
|
289
|
+
} : {})
|
|
290
|
+
});
|
|
291
|
+
// pgp.pg.defaults.max = 70;
|
|
292
|
+
|
|
293
|
+
// /* Casts count/sum/max to bigint. Needs rework to remove casting "+count" and other issues; */
|
|
294
|
+
// pgp.pg.types.setTypeParser(20, BigInt);
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Prevent timestamp casting to ensure we don't lose the microseconds.
|
|
298
|
+
* This is needed to ensure the filters work as expected for a given row
|
|
299
|
+
*
|
|
300
|
+
register(1114, parseTimestamp) // timestamp without time zone
|
|
301
|
+
register(1184, parseTimestampTz) // timestamp with time zone
|
|
302
|
+
*/
|
|
303
|
+
// pgp.pg.types.setTypeParser(1114, v => v); // timestamp without time zone
|
|
304
|
+
// pgp.pg.types.setTypeParser(1184, v => v); // timestamp with time zone
|
|
305
|
+
// pgp.pg.types.setTypeParser(1182, v => v); // date
|
|
306
|
+
pgp.pg.types.setTypeParser(pgp.pg.types.builtins.TIMESTAMP, v => v); // timestamp without time zone
|
|
307
|
+
pgp.pg.types.setTypeParser(pgp.pg.types.builtins.TIMESTAMPTZ, v => v); // timestamp with time zone
|
|
308
|
+
pgp.pg.types.setTypeParser(pgp.pg.types.builtins.DATE, v => v); // date
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
if (dbOptions) {
|
|
312
|
+
Object.assign(pgp.pg.defaults, dbOptions);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
return {
|
|
316
|
+
db: pgp(dbConnection),
|
|
317
|
+
pgp
|
|
318
|
+
};
|
|
319
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { AnyObject, CHANNELS } from "prostgles-types";
|
|
2
|
+
import type { Prostgles, TABLE_METHODS } from "./Prostgles";
|
|
3
|
+
import { PRGLIOSocket } from "./DboBuilder/DboBuilderTypes";
|
|
4
|
+
import { runClientMethod, runClientRequest } from "./runClientRequest";
|
|
5
|
+
import { getErrorAsObject } from "./DboBuilder/dboBuilderUtils";
|
|
6
|
+
|
|
7
|
+
export async function onSocketConnected(this: Prostgles, socket: PRGLIOSocket) {
|
|
8
|
+
if (this.destroyed) {
|
|
9
|
+
console.log("Socket connected to destroyed instance");
|
|
10
|
+
socket.disconnect();
|
|
11
|
+
return
|
|
12
|
+
}
|
|
13
|
+
this.connectedSockets.push(socket);
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
await this.opts.onLog?.({
|
|
17
|
+
type: "connect",
|
|
18
|
+
sid: this.authHandler?.getSID({ socket }),
|
|
19
|
+
socketId: socket.id,
|
|
20
|
+
connectedSocketIds: this.connectedSockets.map(s => s.id)
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
if (!this.db || !this.dbo) throw new Error("db/dbo missing");
|
|
24
|
+
const { dbo, db } = this;
|
|
25
|
+
|
|
26
|
+
if (this.opts.onSocketConnect) {
|
|
27
|
+
try {
|
|
28
|
+
const getUser = async () => { return await this.authHandler?.getClientInfo({ socket }); }
|
|
29
|
+
await this.opts.onSocketConnect({ socket, dbo: dbo as any, db, getUser });
|
|
30
|
+
} catch(error) {
|
|
31
|
+
const connectionError = error instanceof Error? error.message : typeof error === "string"? error : JSON.stringify(error);
|
|
32
|
+
socket.emit(CHANNELS.CONNECTION, { connectionError });
|
|
33
|
+
socket.disconnect();
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
socket.removeAllListeners(CHANNELS.DEFAULT)
|
|
39
|
+
socket.on(CHANNELS.DEFAULT, async (args: SocketRequestParams, cb = (..._callback: any[]) => { /* Empty */}) => {
|
|
40
|
+
runClientRequest.bind(this)({ ...args, type: "socket", socket })
|
|
41
|
+
.then(res => {
|
|
42
|
+
cb(null, res)
|
|
43
|
+
}).catch(err => {
|
|
44
|
+
cb(err);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
socket.on("disconnect", () => {
|
|
49
|
+
|
|
50
|
+
this.dbEventsManager?.removeNotice(socket);
|
|
51
|
+
this.dbEventsManager?.removeNotify(undefined, socket);
|
|
52
|
+
this.connectedSockets = this.connectedSockets.filter(s => s.id !== socket.id);
|
|
53
|
+
this.dboBuilder.queryStreamer.onDisconnect(socket.id);
|
|
54
|
+
this.opts.onLog?.({
|
|
55
|
+
type: "disconnect",
|
|
56
|
+
sid: this.authHandler?.getSID({ socket }),
|
|
57
|
+
socketId: socket.id,
|
|
58
|
+
connectedSocketIds: this.connectedSockets.map(s => s.id)
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
if (this.opts.onSocketDisconnect) {
|
|
62
|
+
const getUser = async () => { return await this.authHandler?.getClientInfo({ socket }); }
|
|
63
|
+
this.opts.onSocketDisconnect({ socket, dbo: dbo as any, db, getUser });
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
socket.removeAllListeners(CHANNELS.METHOD)
|
|
68
|
+
socket.on(CHANNELS.METHOD, async ({ method, params }: SocketMethodRequest, cb = (..._callback: any) => { /* Empty */ }) => {
|
|
69
|
+
runClientMethod.bind(this)({
|
|
70
|
+
type: "socket",
|
|
71
|
+
socket,
|
|
72
|
+
method,
|
|
73
|
+
params
|
|
74
|
+
}).then(res => {
|
|
75
|
+
cb(null, res)
|
|
76
|
+
}).catch(err => {
|
|
77
|
+
makeSocketError(cb, err)
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
this.pushSocketSchema(socket);
|
|
82
|
+
} catch (e) {
|
|
83
|
+
console.trace("setSocketEvents: ", e)
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
export function makeSocketError(cb: (err: AnyObject) => void, err: any) {
|
|
89
|
+
cb(getErrorAsObject(err));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
type SocketRequestParams = {
|
|
93
|
+
tableName: string;
|
|
94
|
+
command: typeof TABLE_METHODS[number];
|
|
95
|
+
param1: any;
|
|
96
|
+
param2: any;
|
|
97
|
+
param3: any;
|
|
98
|
+
}
|
|
99
|
+
type SocketMethodRequest = {
|
|
100
|
+
method: string;
|
|
101
|
+
params: any;
|
|
102
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { AnyObject, TableHandler, UserLike, getKeys, pickKeys } from "prostgles-types";
|
|
2
|
+
import { ExpressReq } from "./Auth/AuthTypes";
|
|
3
|
+
import { LocalParams, PRGLIOSocket } from "./DboBuilder/DboBuilder";
|
|
4
|
+
import { parseFieldFilter } from "./DboBuilder/ViewHandler/parseFieldFilter";
|
|
5
|
+
import { canRunSQL } from "./DboBuilder/runSQL";
|
|
6
|
+
import { Prostgles } from "./Prostgles";
|
|
7
|
+
|
|
8
|
+
type ReqInfo = {
|
|
9
|
+
type: "socket";
|
|
10
|
+
socket: PRGLIOSocket;
|
|
11
|
+
httpReq?: undefined;
|
|
12
|
+
} | {
|
|
13
|
+
type: "http";
|
|
14
|
+
httpReq: ExpressReq;
|
|
15
|
+
socket?: undefined;
|
|
16
|
+
}
|
|
17
|
+
type ReqInfoClient = {
|
|
18
|
+
socket: PRGLIOSocket;
|
|
19
|
+
} | {
|
|
20
|
+
httpReq: ExpressReq;
|
|
21
|
+
}
|
|
22
|
+
type Args = ReqInfo & {
|
|
23
|
+
tableName: string;
|
|
24
|
+
command: string;
|
|
25
|
+
param1: any;
|
|
26
|
+
param2: any;
|
|
27
|
+
param3: any;
|
|
28
|
+
};
|
|
29
|
+
const SOCKET_ONLY_COMMANDS = ["subscribe", "subscribeOne", "sync"];
|
|
30
|
+
|
|
31
|
+
const getReqInfoClient = (reqInfo: ReqInfo): ReqInfoClient => {
|
|
32
|
+
if(reqInfo.type === "socket"){
|
|
33
|
+
return { socket: reqInfo.socket };
|
|
34
|
+
}
|
|
35
|
+
return { httpReq: reqInfo.httpReq };
|
|
36
|
+
}
|
|
37
|
+
export const runClientRequest = async function(this: Prostgles, args: Args){
|
|
38
|
+
/* Channel name will only include client-sent params so we ignore table_rules enforced params */
|
|
39
|
+
if ((args.type === "socket" && !args.socket) || (args.type === "http" && !args.httpReq) || !this.authHandler || !this.publishParser || !this.dbo) {
|
|
40
|
+
throw "socket/httpReq or authhandler missing";
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const { tableName, command, param1, param2, param3 } = args;
|
|
44
|
+
|
|
45
|
+
if(args.type !== "socket" && SOCKET_ONLY_COMMANDS.includes(command)){
|
|
46
|
+
throw "The following commands cannot be completed over a non-websocket connection: " + SOCKET_ONLY_COMMANDS;
|
|
47
|
+
}
|
|
48
|
+
const reqInfo = getReqInfoClient(args);
|
|
49
|
+
const clientInfo = await this.authHandler.getClientInfo(args);
|
|
50
|
+
const valid_table_command_rules = await this.publishParser.getValidatedRequestRule({ tableName, command, localParams: reqInfo }, clientInfo);
|
|
51
|
+
if (valid_table_command_rules) {
|
|
52
|
+
const sessionUser: UserLike | undefined = !clientInfo?.user? undefined : {
|
|
53
|
+
...parseFieldFilter(clientInfo.sessionFields ?? [] as any, false, Object.keys(clientInfo.user)),
|
|
54
|
+
...pickKeys(clientInfo.user, ["id", "type"]) as UserLike,
|
|
55
|
+
}
|
|
56
|
+
const localParams: LocalParams = { ...reqInfo, isRemoteRequest: { user: sessionUser } }
|
|
57
|
+
if(param3 && (param3 as LocalParams).returnQuery){
|
|
58
|
+
const isAllowed = await canRunSQL(this, localParams);
|
|
59
|
+
if(isAllowed){
|
|
60
|
+
localParams.returnQuery = (param3 as LocalParams).returnQuery;
|
|
61
|
+
} else {
|
|
62
|
+
throw "Must be allowed to run sql to use returnQuery";
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const tableHandler = this.dbo[tableName];
|
|
66
|
+
if(!tableHandler || !tableHandler.column_names) throw `Invalid tableName ${tableName} provided`;
|
|
67
|
+
const method = tableHandler[command as keyof TableHandler];
|
|
68
|
+
if(!method) throw `Invalid command ${command} provided`;
|
|
69
|
+
//@ts-ignore
|
|
70
|
+
return this.dbo[tableName][command](param1, param2, param3, valid_table_command_rules, localParams);
|
|
71
|
+
} else {
|
|
72
|
+
throw `Invalid OR disallowed request: ${tableName}.${command} `;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export const clientCanRunSqlRequest = async function(this: Prostgles, args: ReqInfo){
|
|
77
|
+
const reqInfo = getReqInfoClient(args);
|
|
78
|
+
if(!this.opts.publishRawSQL || typeof this.opts.publishRawSQL !== "function"){
|
|
79
|
+
return { allowed: false, reqInfo }
|
|
80
|
+
}
|
|
81
|
+
const canRunSQL = async () => {
|
|
82
|
+
if(!this.authHandler){
|
|
83
|
+
throw "authHandler missing";
|
|
84
|
+
}
|
|
85
|
+
const publishParams = await this.publishParser?.getPublishParams(reqInfo);
|
|
86
|
+
const res = await this.opts.publishRawSQL?.(publishParams as any);
|
|
87
|
+
return Boolean(res && typeof res === "boolean" || res === "*");
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const allowed = await canRunSQL();
|
|
91
|
+
return { allowed, reqInfo };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
type ArgsSql = ReqInfo & {
|
|
95
|
+
query: string;
|
|
96
|
+
args?: AnyObject | any[];
|
|
97
|
+
options?: any;
|
|
98
|
+
}
|
|
99
|
+
export const runClientSqlRequest = async function(this: Prostgles, params: ArgsSql){
|
|
100
|
+
const { allowed, reqInfo } = await clientCanRunSqlRequest.bind(this)(params);
|
|
101
|
+
if(!allowed){
|
|
102
|
+
throw "Not allowed to execute sql";
|
|
103
|
+
}
|
|
104
|
+
if (!this.dbo?.sql) throw "Internal error: sql handler missing";
|
|
105
|
+
const { query, args, options } = params;
|
|
106
|
+
return this.dbo.sql(query, args, options, reqInfo);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
type ArgsMethod = ReqInfo & {
|
|
112
|
+
method: string;
|
|
113
|
+
params?: any[]
|
|
114
|
+
}
|
|
115
|
+
export const runClientMethod = async function(this: Prostgles, reqArgs: ArgsMethod){
|
|
116
|
+
|
|
117
|
+
const reqInfo = getReqInfoClient(reqArgs);
|
|
118
|
+
const { method, params = [] } = reqArgs;
|
|
119
|
+
const methods = await this.publishParser?.getAllowedMethods(reqInfo);
|
|
120
|
+
|
|
121
|
+
if (!methods || !methods[method]) {
|
|
122
|
+
throw ("Disallowed/missing method " + JSON.stringify(method));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const methodDef = methods[method]!;
|
|
126
|
+
const onRun = (typeof methodDef === "function" || typeof (methodDef as any).then === "function")? (methodDef as (...args: any) => Promise<void>) : methodDef.run;
|
|
127
|
+
const res = await onRun(...params);
|
|
128
|
+
return res;
|
|
129
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { AnyObject } from "prostgles-types";
|
|
2
|
+
|
|
3
|
+
const shortestDistanceNode = (distances: AnyObject, visited: AnyObject) => {
|
|
4
|
+
let shortest = null;
|
|
5
|
+
|
|
6
|
+
for (const node in distances) {
|
|
7
|
+
const currentIsShortest =
|
|
8
|
+
shortest === null || distances[node] < distances[shortest];
|
|
9
|
+
if (currentIsShortest && !visited.includes(node)) {
|
|
10
|
+
shortest = node;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
return shortest;
|
|
14
|
+
};
|
|
15
|
+
export type Graph = {
|
|
16
|
+
[key: string]: { [key: string]: number }
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const findShortestPath = (graph: Graph, startNode: string, endNode: string): { distance: number, path: string[] } => {
|
|
20
|
+
// establish object for recording distances from the start node
|
|
21
|
+
let distances: AnyObject = {};
|
|
22
|
+
distances[endNode] = "Infinity";
|
|
23
|
+
distances = Object.assign(distances, graph[startNode]);
|
|
24
|
+
|
|
25
|
+
// track paths
|
|
26
|
+
const parents: AnyObject = { endNode: null };
|
|
27
|
+
for (const child in graph[startNode]) {
|
|
28
|
+
parents[child] = startNode;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// track nodes that have already been visited
|
|
32
|
+
const visited: AnyObject = [];
|
|
33
|
+
|
|
34
|
+
// find the nearest node
|
|
35
|
+
let node = shortestDistanceNode(distances, visited);
|
|
36
|
+
|
|
37
|
+
// for that node
|
|
38
|
+
while (node) {
|
|
39
|
+
// find its distance from the start node & its child nodes
|
|
40
|
+
const distance = distances[node];
|
|
41
|
+
const children = graph[node];
|
|
42
|
+
// for each of those child nodes
|
|
43
|
+
for (const child in children) {
|
|
44
|
+
// make sure each child node is not the start node
|
|
45
|
+
if (String(child) === String(startNode)) {
|
|
46
|
+
continue;
|
|
47
|
+
} else {
|
|
48
|
+
// save the distance from the start node to the child node
|
|
49
|
+
const newdistance = distance + children[child];
|
|
50
|
+
// if there's no recorded distance from the start node to the child node in the distances object
|
|
51
|
+
// or if the recorded distance is shorter than the previously stored distance from the start node to the child node
|
|
52
|
+
// save the distance to the object
|
|
53
|
+
// record the path
|
|
54
|
+
if (!distances[child] || distances[child] > newdistance) {
|
|
55
|
+
distances[child] = newdistance;
|
|
56
|
+
parents[child] = node;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// move the node to the visited set
|
|
61
|
+
visited.push(node);
|
|
62
|
+
// move to the nearest neighbor node
|
|
63
|
+
node = shortestDistanceNode(distances, visited);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// using the stored paths from start node to end node
|
|
67
|
+
// record the shortest path
|
|
68
|
+
const shortestPath = [endNode];
|
|
69
|
+
let parent = parents[endNode];
|
|
70
|
+
while (parent) {
|
|
71
|
+
shortestPath.push(parent);
|
|
72
|
+
parent = parents[parent];
|
|
73
|
+
}
|
|
74
|
+
shortestPath.reverse();
|
|
75
|
+
|
|
76
|
+
// return the shortest path from start node to end node & its distance
|
|
77
|
+
const results = {
|
|
78
|
+
distance: distances[endNode],
|
|
79
|
+
path: shortestPath,
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
return results;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
/* Usage:
|
|
86
|
+
|
|
87
|
+
const graph = {
|
|
88
|
+
start: { A: 5, B: 2 },
|
|
89
|
+
A: { start: 1, C: 4, D: 2 },
|
|
90
|
+
B: { A: 8, D: 7 },
|
|
91
|
+
C: { D: 6, end: 3 },
|
|
92
|
+
D: { end: 1 },
|
|
93
|
+
end: {},
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
findShortestPath(graph, 'start', 'end');
|
|
97
|
+
|
|
98
|
+
{
|
|
99
|
+
"distance": 8,
|
|
100
|
+
"path": [
|
|
101
|
+
"start",
|
|
102
|
+
"A",
|
|
103
|
+
"D",
|
|
104
|
+
"end"
|
|
105
|
+
]
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
// The graph is unidirectional
|
|
112
|
+
|
|
113
|
+
const graph = {
|
|
114
|
+
start: { A: 5 },
|
|
115
|
+
end: { A: 1 },
|
|
116
|
+
A: { start: 1, end: 1 },
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
findShortestPath(graph, 'start', 'end');
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
*/
|