prostgles-server 2.0.178 → 2.0.179
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/AuthHandler.d.ts +4 -4
- package/dist/AuthHandler.d.ts.map +1 -1
- package/dist/DBSchemaBuilder.d.ts +6 -6
- package/dist/DBSchemaBuilder.d.ts.map +1 -1
- package/dist/DBSchemaBuilder.js +25 -8
- package/dist/DBSchemaBuilder.js.map +1 -1
- package/dist/DboBuilder.d.ts +20 -21
- package/dist/DboBuilder.d.ts.map +1 -1
- package/dist/DboBuilder.js +1 -1
- package/dist/DboBuilder.js.map +1 -1
- package/dist/Prostgles.d.ts +8 -10
- package/dist/Prostgles.d.ts.map +1 -1
- package/dist/Prostgles.js.map +1 -1
- package/dist/PublishParser.d.ts +37 -37
- package/dist/PublishParser.d.ts.map +1 -1
- package/dist/PublishParser.js.map +1 -1
- package/dist/index.d.ts +2 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/lib/AuthHandler.d.ts +148 -0
- package/lib/AuthHandler.d.ts.map +1 -0
- package/lib/AuthHandler.js +411 -0
- package/lib/AuthHandler.ts +3 -3
- package/lib/DBEventsManager.d.ts +38 -0
- package/lib/DBEventsManager.d.ts.map +1 -0
- package/lib/DBEventsManager.js +136 -0
- package/lib/DBSchemaBuilder.d.ts +11 -0
- package/lib/DBSchemaBuilder.d.ts.map +1 -0
- package/lib/DBSchemaBuilder.js +102 -0
- package/lib/DBSchemaBuilder.ts +62 -27
- package/lib/DboBuilder.d.ts +428 -0
- package/lib/DboBuilder.d.ts.map +1 -0
- package/lib/DboBuilder.js +3078 -0
- package/lib/DboBuilder.ts +25 -25
- package/lib/FileManager.d.ts +168 -0
- package/lib/FileManager.d.ts.map +1 -0
- package/lib/FileManager.js +474 -0
- package/lib/Filtering.d.ts +15 -0
- package/lib/Filtering.d.ts.map +1 -0
- package/lib/Filtering.js +299 -0
- package/lib/PostgresNotifListenManager.d.ts +27 -0
- package/lib/PostgresNotifListenManager.d.ts.map +1 -0
- package/lib/PostgresNotifListenManager.js +122 -0
- package/lib/Prostgles.d.ts +193 -0
- package/lib/Prostgles.d.ts.map +1 -0
- package/lib/Prostgles.js +579 -0
- package/lib/Prostgles.ts +6 -6
- package/lib/PubSubManager.d.ts +157 -0
- package/lib/PubSubManager.d.ts.map +1 -0
- package/lib/PubSubManager.js +1400 -0
- package/lib/PublishParser.d.ts +262 -0
- package/lib/PublishParser.d.ts.map +1 -0
- package/lib/PublishParser.js +390 -0
- package/lib/PublishParser.ts +39 -38
- package/lib/QueryBuilder.d.ts +124 -0
- package/lib/QueryBuilder.d.ts.map +1 -0
- package/lib/QueryBuilder.js +1349 -0
- package/lib/SyncReplication.d.ts +34 -0
- package/lib/SyncReplication.d.ts.map +1 -0
- package/lib/SyncReplication.js +411 -0
- package/lib/TableConfig.d.ts +175 -0
- package/lib/TableConfig.d.ts.map +1 -0
- package/lib/TableConfig.js +231 -0
- package/lib/index.d.ts +10 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +45 -0
- package/lib/index.ts +3 -4
- package/lib/shortestPath.d.ts +10 -0
- package/lib/shortestPath.d.ts.map +1 -0
- package/lib/shortestPath.js +111 -0
- package/lib/utils.d.ts +2 -0
- package/lib/utils.d.ts.map +1 -0
- package/lib/utils.js +5 -0
- package/package.json +3 -3
- package/tests/client/PID.txt +1 -1
- package/tests/client/index.d.ts +1 -1
- package/tests/client/index.d.ts.map +1 -1
- package/tests/client_only_queries.d.ts +4 -0
- package/tests/client_only_queries.d.ts.map +1 -0
- package/tests/isomorphic_queries.d.ts +6 -0
- package/tests/isomorphic_queries.d.ts.map +1 -0
- package/tests/server/DBoGenerated.d.ts +97 -193
- package/tests/server/dboTypeCheck.d.ts +2 -0
- package/tests/server/dboTypeCheck.d.ts.map +1 -0
- package/tests/server/dboTypeCheck.js +14 -0
- package/tests/server/dboTypeCheck.ts +17 -0
- package/tests/server/index.d.ts +2 -0
- package/tests/server/index.d.ts.map +1 -0
- package/tests/server/index.js +11 -11
- package/tests/server/index.ts +23 -16
- package/tests/server/package-lock.json +5 -5
- package/tests/server/publishTypeCheck.d.ts +2 -0
- package/tests/server/publishTypeCheck.d.ts.map +1 -0
- package/tests/server/publishTypeCheck.js +120 -0
- package/tests/server/publishTypeCheck.ts +129 -0
- package/tests/server/tsconfig.json +4 -5
- package/tests/server_only_queries.d.ts +2 -0
- package/tests/server_only_queries.d.ts.map +1 -0
package/lib/Prostgles.js
ADDED
|
@@ -0,0 +1,579 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*---------------------------------------------------------------------------------------------
|
|
3
|
+
* Copyright (c) Stefan L. All rights reserved.
|
|
4
|
+
* Licensed under the MIT License. See LICENSE in the project root for license information.
|
|
5
|
+
*--------------------------------------------------------------------------------------------*/
|
|
6
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
7
|
+
if (k2 === undefined) k2 = k;
|
|
8
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
9
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
10
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
11
|
+
}
|
|
12
|
+
Object.defineProperty(o, k2, desc);
|
|
13
|
+
}) : (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
o[k2] = m[k];
|
|
16
|
+
}));
|
|
17
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
18
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
19
|
+
}) : function(o, v) {
|
|
20
|
+
o["default"] = v;
|
|
21
|
+
});
|
|
22
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
23
|
+
if (mod && mod.__esModule) return mod;
|
|
24
|
+
var result = {};
|
|
25
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
26
|
+
__setModuleDefault(result, mod);
|
|
27
|
+
return result;
|
|
28
|
+
};
|
|
29
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
30
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
31
|
+
};
|
|
32
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
33
|
+
exports.isSuperUser = exports.Prostgles = exports.JOIN_TYPES = exports.TABLE_METHODS = void 0;
|
|
34
|
+
const promise = __importStar(require("bluebird"));
|
|
35
|
+
const pgPromise = __importStar(require("pg-promise"));
|
|
36
|
+
const FileManager_1 = __importDefault(require("./FileManager"));
|
|
37
|
+
const pkgj = require('../package.json');
|
|
38
|
+
const version = pkgj.version;
|
|
39
|
+
const AuthHandler_1 = __importDefault(require("./AuthHandler"));
|
|
40
|
+
console.log("Add a basic auth mode where user and sessions table are created");
|
|
41
|
+
const TableConfig_1 = __importDefault(require("./TableConfig"));
|
|
42
|
+
const utils_1 = require("./utils");
|
|
43
|
+
const DboBuilder_1 = require("./DboBuilder");
|
|
44
|
+
const PubSubManager_1 = require("./PubSubManager");
|
|
45
|
+
const prostgles_types_1 = require("prostgles-types");
|
|
46
|
+
const PublishParser_1 = require("./PublishParser");
|
|
47
|
+
const DBEventsManager_1 = require("./DBEventsManager");
|
|
48
|
+
exports.TABLE_METHODS = ["update", "find", "findOne", "insert", "delete", "upsert"];
|
|
49
|
+
function getDbConnection(dbConnection, options, debugQueries = false, onNotice) {
|
|
50
|
+
let pgp = pgPromise({
|
|
51
|
+
promiseLib: promise,
|
|
52
|
+
...(debugQueries ? {
|
|
53
|
+
query: function (e) {
|
|
54
|
+
console.log({ psql: e.query, params: e.params });
|
|
55
|
+
},
|
|
56
|
+
} : {}),
|
|
57
|
+
...((onNotice || debugQueries) ? {
|
|
58
|
+
connect: function (client, dc, isFresh) {
|
|
59
|
+
if (isFresh && !client.listeners('notice').length) {
|
|
60
|
+
client.on('notice', function (msg) {
|
|
61
|
+
if (onNotice) {
|
|
62
|
+
onNotice(msg, (0, utils_1.get)(msg, "message"));
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
console.log("notice: %j", (0, utils_1.get)(msg, "message"));
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
if (isFresh && !client.listeners('error').length) {
|
|
70
|
+
client.on('error', function (msg) {
|
|
71
|
+
if (onNotice) {
|
|
72
|
+
onNotice(msg, (0, utils_1.get)(msg, "message"));
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
console.log("error: %j", (0, utils_1.get)(msg, "message"));
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
} : {})
|
|
81
|
+
});
|
|
82
|
+
pgp.pg.defaults.max = 70;
|
|
83
|
+
// /* Casts count/sum/max to bigint. Needs rework to remove casting "+count" and other issues; */
|
|
84
|
+
// pgp.pg.types.setTypeParser(20, BigInt);
|
|
85
|
+
if (options) {
|
|
86
|
+
Object.assign(pgp.pg.defaults, options);
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
db: pgp(dbConnection),
|
|
90
|
+
pgp
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
exports.JOIN_TYPES = ["one-many", "many-one", "one-one", "many-many"];
|
|
94
|
+
const DEFAULT_KEYWORDS = {
|
|
95
|
+
$filter: "$filter",
|
|
96
|
+
$and: "$and",
|
|
97
|
+
$or: "$or",
|
|
98
|
+
$not: "$not"
|
|
99
|
+
};
|
|
100
|
+
const fs = __importStar(require("fs"));
|
|
101
|
+
class Prostgles {
|
|
102
|
+
constructor(params) {
|
|
103
|
+
this.opts = {
|
|
104
|
+
DEBUG_MODE: false,
|
|
105
|
+
dbConnection: {
|
|
106
|
+
host: "localhost",
|
|
107
|
+
port: 5432,
|
|
108
|
+
application_name: "prostgles_app"
|
|
109
|
+
},
|
|
110
|
+
onReady: () => { },
|
|
111
|
+
schema: "public",
|
|
112
|
+
watchSchema: false,
|
|
113
|
+
watchSchemaType: "queries",
|
|
114
|
+
};
|
|
115
|
+
this.keywords = DEFAULT_KEYWORDS;
|
|
116
|
+
this.loaded = false;
|
|
117
|
+
this.destroyed = false;
|
|
118
|
+
this.refreshDBO = async () => {
|
|
119
|
+
if (this._dboBuilder)
|
|
120
|
+
this._dboBuilder.destroy();
|
|
121
|
+
this.dboBuilder = await DboBuilder_1.DboBuilder.create(this);
|
|
122
|
+
if (!this.dboBuilder)
|
|
123
|
+
throw "this.dboBuilder";
|
|
124
|
+
this.dbo = this.dboBuilder.dbo;
|
|
125
|
+
return this.dbo;
|
|
126
|
+
};
|
|
127
|
+
this.isSuperUser = false;
|
|
128
|
+
this.connectedSockets = [];
|
|
129
|
+
this.pushSocketSchema = async (socket) => {
|
|
130
|
+
let auth = await this.authHandler?.makeSocketAuth(socket) || {};
|
|
131
|
+
// let needType = this.publishRawSQL && typeof this.publishRawSQL === "function";
|
|
132
|
+
// let DATA_TYPES = !needType? [] : await this.db.any("SELECT oid, typname FROM pg_type");
|
|
133
|
+
// let USER_TABLES = !needType? [] : await this.db.any("SELECT relid, relname FROM pg_catalog.pg_statio_user_tables");
|
|
134
|
+
const { dbo, db, pgp, publishParser } = this;
|
|
135
|
+
let fullSchema;
|
|
136
|
+
let publishValidationError;
|
|
137
|
+
let rawSQL = false;
|
|
138
|
+
try {
|
|
139
|
+
if (!publishParser)
|
|
140
|
+
throw "publishParser undefined";
|
|
141
|
+
fullSchema = await publishParser.getSchemaFromPublish(socket);
|
|
142
|
+
}
|
|
143
|
+
catch (e) {
|
|
144
|
+
publishValidationError = "Server Error: PUBLISH VALIDATION ERROR";
|
|
145
|
+
console.error(`\nProstgles PUBLISH VALIDATION ERROR (after socket connected):\n ->`, e);
|
|
146
|
+
}
|
|
147
|
+
socket.prostgles = socket.prostgles || {};
|
|
148
|
+
socket.prostgles.schema = fullSchema?.schema;
|
|
149
|
+
/* RUN Raw sql from client IF PUBLISHED
|
|
150
|
+
*/
|
|
151
|
+
if (this.opts.publishRawSQL && typeof this.opts.publishRawSQL === "function") {
|
|
152
|
+
const canRunSQL = async () => {
|
|
153
|
+
const publishParams = await this.publishParser?.getPublishParams({ socket });
|
|
154
|
+
let res = await this.opts.publishRawSQL?.(publishParams);
|
|
155
|
+
return Boolean(res && typeof res === "boolean" || res === "*");
|
|
156
|
+
};
|
|
157
|
+
if (await canRunSQL()) {
|
|
158
|
+
socket.removeAllListeners(prostgles_types_1.CHANNELS.SQL);
|
|
159
|
+
socket.on(prostgles_types_1.CHANNELS.SQL, async ({ query, params, options }, cb = (...callback) => { }) => {
|
|
160
|
+
if (!this.dbo?.sql)
|
|
161
|
+
throw "Internal error: sql handler missing";
|
|
162
|
+
this.dbo.sql(query, params, options, { socket }).then(res => {
|
|
163
|
+
cb(null, res);
|
|
164
|
+
}).catch(err => {
|
|
165
|
+
makeSocketError(cb, err);
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
if (db) {
|
|
169
|
+
// let allTablesViews = await db.any(STEP2_GET_ALL_TABLES_AND_COLUMNS);
|
|
170
|
+
// fullSchema = allTablesViews;
|
|
171
|
+
rawSQL = true;
|
|
172
|
+
}
|
|
173
|
+
else
|
|
174
|
+
console.error("db missing");
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
const { schema, tables } = fullSchema ?? { schema: {}, tables: [] };
|
|
178
|
+
let joinTables2 = [];
|
|
179
|
+
if (this.opts.joins) {
|
|
180
|
+
// joinTables = Array.from(new Set(flat(this.dboBuilder.getJoins().map(j => j.tables)).filter(t => schema[t])));
|
|
181
|
+
let _joinTables2 = this.dboBuilder.getJoinPaths()
|
|
182
|
+
.filter(jp => ![jp.t1, jp.t2].find(t => !schema[t] || !schema[t].findOne)).map(jp => [jp.t1, jp.t2].sort());
|
|
183
|
+
_joinTables2.map(jt => {
|
|
184
|
+
if (!joinTables2.find(_jt => _jt.join() === jt.join())) {
|
|
185
|
+
joinTables2.push(jt);
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
const methods = await publishParser?.getMethods(socket);
|
|
190
|
+
const clientSchema = {
|
|
191
|
+
schema,
|
|
192
|
+
methods: (0, prostgles_types_1.getKeys)(methods),
|
|
193
|
+
tableSchema: tables,
|
|
194
|
+
rawSQL,
|
|
195
|
+
joinTables: joinTables2,
|
|
196
|
+
auth,
|
|
197
|
+
version,
|
|
198
|
+
err: publishValidationError
|
|
199
|
+
};
|
|
200
|
+
socket.emit(prostgles_types_1.CHANNELS.SCHEMA, clientSchema);
|
|
201
|
+
};
|
|
202
|
+
if (!params)
|
|
203
|
+
throw "ProstglesInitOptions missing";
|
|
204
|
+
if (!params.io)
|
|
205
|
+
console.warn("io missing. WebSockets will not be set up");
|
|
206
|
+
// TODO: find an exact keyof T<->arr TS matching method
|
|
207
|
+
let config = [
|
|
208
|
+
"transactions", "joins", "tsGeneratedTypesDir",
|
|
209
|
+
"onReady", "dbConnection", "dbOptions", "publishMethods", "io",
|
|
210
|
+
"publish", "schema", "publishRawSQL", "wsChannelNamePrefix", "onSocketConnect",
|
|
211
|
+
"onSocketDisconnect", "sqlFilePath", "auth", "DEBUG_MODE", "watchSchema", "watchSchemaType",
|
|
212
|
+
"fileTable", "tableConfig"
|
|
213
|
+
];
|
|
214
|
+
const unknownParams = Object.keys(params).filter((key) => !config.includes(key));
|
|
215
|
+
if (unknownParams.length) {
|
|
216
|
+
console.error(`Unrecognised ProstglesInitOptions params: ${unknownParams.join()}`);
|
|
217
|
+
}
|
|
218
|
+
Object.assign(this.opts, params);
|
|
219
|
+
/* set defaults */
|
|
220
|
+
if (this.opts?.fileTable) {
|
|
221
|
+
this.opts.fileTable.tableName = this.opts?.fileTable?.tableName || "media";
|
|
222
|
+
}
|
|
223
|
+
this.opts.schema = this.opts.schema || "public";
|
|
224
|
+
this.keywords = {
|
|
225
|
+
...DEFAULT_KEYWORDS,
|
|
226
|
+
...params.keywords,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
get dboBuilder() {
|
|
230
|
+
if (!this._dboBuilder)
|
|
231
|
+
throw "get dboBuilder: it's undefined";
|
|
232
|
+
return this._dboBuilder;
|
|
233
|
+
}
|
|
234
|
+
set dboBuilder(d) {
|
|
235
|
+
this._dboBuilder = d;
|
|
236
|
+
}
|
|
237
|
+
isMedia(tableName) {
|
|
238
|
+
return this.opts?.fileTable?.tableName === tableName;
|
|
239
|
+
}
|
|
240
|
+
async onSchemaChange(event) {
|
|
241
|
+
const { watchSchema, onReady, tsGeneratedTypesDir } = this.opts;
|
|
242
|
+
if (watchSchema && this.loaded) {
|
|
243
|
+
console.log("Schema changed");
|
|
244
|
+
const { query } = event;
|
|
245
|
+
if (typeof query === "string" && query.includes(PubSubManager_1.PubSubManager.EXCLUDE_QUERY_FROM_SCHEMA_WATCH_ID)) {
|
|
246
|
+
console.log("Schema change event excluded from triggers due to EXCLUDE_QUERY_FROM_SCHEMA_WATCH_ID");
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
if (typeof watchSchema === "function") {
|
|
250
|
+
/* Only call the provided func */
|
|
251
|
+
watchSchema(event);
|
|
252
|
+
}
|
|
253
|
+
else if (watchSchema === "hotReloadMode") {
|
|
254
|
+
if (tsGeneratedTypesDir) {
|
|
255
|
+
/* Hot reload integration. Will only touch tsGeneratedTypesDir */
|
|
256
|
+
console.log("watchSchema: Re-writing TS schema");
|
|
257
|
+
await this.refreshDBO();
|
|
258
|
+
this.writeDBSchema(true);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
else if (watchSchema === true || "checkIntervalMillis" in watchSchema) {
|
|
262
|
+
/* Full re-init. Sockets must reconnect */
|
|
263
|
+
console.log("watchSchema: Full re-initialisation");
|
|
264
|
+
this.init(onReady);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
checkDb() {
|
|
269
|
+
if (!this.db || !this.db.connect)
|
|
270
|
+
throw "something went wrong getting a db connection";
|
|
271
|
+
}
|
|
272
|
+
getTSFileName() {
|
|
273
|
+
const fileName = "DBoGenerated.d.ts"; //`dbo_${this.schema}_types.ts`;
|
|
274
|
+
const fullPath = (this.opts.tsGeneratedTypesDir || "") + fileName;
|
|
275
|
+
return { fileName, fullPath };
|
|
276
|
+
}
|
|
277
|
+
getFileText(fullPath, format = "utf8") {
|
|
278
|
+
return new Promise((resolve, reject) => {
|
|
279
|
+
fs.readFile(fullPath, 'utf8', function (err, data) {
|
|
280
|
+
if (err)
|
|
281
|
+
reject(err);
|
|
282
|
+
else
|
|
283
|
+
resolve(data);
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
writeDBSchema(force = false) {
|
|
288
|
+
if (this.opts.tsGeneratedTypesDir) {
|
|
289
|
+
const { fullPath, fileName } = this.getTSFileName();
|
|
290
|
+
const header = `/* This file was generated by Prostgles \n` +
|
|
291
|
+
// `* ${(new Date).toUTCString()} \n`
|
|
292
|
+
`*/ \n\n `;
|
|
293
|
+
const fileContent = header + this.dboBuilder.tsTypesDefinition;
|
|
294
|
+
fs.readFile(fullPath, 'utf8', function (err, data) {
|
|
295
|
+
if (err || (force || data !== fileContent)) {
|
|
296
|
+
fs.writeFileSync(fullPath, fileContent);
|
|
297
|
+
console.log("Prostgles: Created typescript schema definition file: \n " + fileName);
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
else if (force) {
|
|
302
|
+
console.error("Schema changed. tsGeneratedTypesDir needs to be set to reload server");
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
async init(onReady) {
|
|
306
|
+
this.loaded = false;
|
|
307
|
+
if (this.opts.watchSchema === "hotReloadMode" && !this.opts.tsGeneratedTypesDir) {
|
|
308
|
+
throw "tsGeneratedTypesDir option is needed for watchSchema: hotReloadMode to work ";
|
|
309
|
+
}
|
|
310
|
+
else if (this.opts.watchSchema &&
|
|
311
|
+
typeof this.opts.watchSchema === "object" &&
|
|
312
|
+
"checkIntervalMillis" in this.opts.watchSchema &&
|
|
313
|
+
typeof this.opts.watchSchema.checkIntervalMillis === "number") {
|
|
314
|
+
if (this.schema_checkIntervalMillis) {
|
|
315
|
+
clearInterval(this.schema_checkIntervalMillis);
|
|
316
|
+
this.schema_checkIntervalMillis = setInterval(async () => {
|
|
317
|
+
const dbuilder = await DboBuilder_1.DboBuilder.create(this);
|
|
318
|
+
if (dbuilder.tsTypesDefinition !== this.dboBuilder.tsTypesDefinition) {
|
|
319
|
+
this.refreshDBO();
|
|
320
|
+
this.init(onReady);
|
|
321
|
+
}
|
|
322
|
+
}, this.opts.watchSchema.checkIntervalMillis);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
/* 1. Connect to db */
|
|
326
|
+
if (!this.db) {
|
|
327
|
+
const { db, pgp } = getDbConnection(this.opts.dbConnection, this.opts.dbOptions, this.opts.DEBUG_MODE, notice => {
|
|
328
|
+
if (this.opts.onNotice)
|
|
329
|
+
this.opts.onNotice(notice);
|
|
330
|
+
if (this.dbEventsManager) {
|
|
331
|
+
this.dbEventsManager.onNotice(notice);
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
this.db = db;
|
|
335
|
+
this.pgp = pgp;
|
|
336
|
+
this.isSuperUser = await isSuperUser(db);
|
|
337
|
+
}
|
|
338
|
+
this.checkDb();
|
|
339
|
+
const db = this.db;
|
|
340
|
+
const pgp = this.pgp;
|
|
341
|
+
/* 2. Execute any SQL file if provided */
|
|
342
|
+
if (this.opts.sqlFilePath) {
|
|
343
|
+
await this.runSQLFile(this.opts.sqlFilePath);
|
|
344
|
+
}
|
|
345
|
+
try {
|
|
346
|
+
await this.refreshDBO();
|
|
347
|
+
if (this.opts.tableConfig) {
|
|
348
|
+
this.tableConfigurator = new TableConfig_1.default(this);
|
|
349
|
+
try {
|
|
350
|
+
await this.tableConfigurator.init();
|
|
351
|
+
}
|
|
352
|
+
catch (e) {
|
|
353
|
+
console.error("TableConfigurator: ", e);
|
|
354
|
+
throw e;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
/* 3. Make DBO object from all tables and views */
|
|
358
|
+
await this.refreshDBO();
|
|
359
|
+
/* Create media table if required */
|
|
360
|
+
if (this.opts.fileTable) {
|
|
361
|
+
const { awsS3Config, localConfig, imageOptions } = this.opts.fileTable;
|
|
362
|
+
await this.refreshDBO();
|
|
363
|
+
if (!awsS3Config && !localConfig)
|
|
364
|
+
throw "fileTable missing param: Must provide awsS3Config OR localConfig";
|
|
365
|
+
//@ts-ignore
|
|
366
|
+
this.fileManager = new FileManager_1.default(awsS3Config || localConfig, imageOptions);
|
|
367
|
+
try {
|
|
368
|
+
await this.fileManager.init(this);
|
|
369
|
+
}
|
|
370
|
+
catch (e) {
|
|
371
|
+
console.error("FileManager: ", e);
|
|
372
|
+
throw e;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
await this.refreshDBO();
|
|
376
|
+
if (this.opts.publish) {
|
|
377
|
+
if (!this.opts.io)
|
|
378
|
+
console.warn("IO missing. Publish has no effect without io");
|
|
379
|
+
/* 3.9 Check auth config */
|
|
380
|
+
this.authHandler = new AuthHandler_1.default(this);
|
|
381
|
+
await this.authHandler.init();
|
|
382
|
+
this.publishParser = new PublishParser_1.PublishParser(this.opts.publish, this.opts.publishMethods, this.opts.publishRawSQL, this.dbo, this.db, this);
|
|
383
|
+
this.dboBuilder.publishParser = this.publishParser;
|
|
384
|
+
/* 4. Set publish and auth listeners */
|
|
385
|
+
await this.setSocketEvents();
|
|
386
|
+
}
|
|
387
|
+
else if (this.opts.auth)
|
|
388
|
+
throw "Auth config does not work without publish";
|
|
389
|
+
// if(this.watchSchema){
|
|
390
|
+
// if(!(await isSuperUser(db))) throw "Cannot watchSchema without a super user schema. Set watchSchema=false or provide a super user";
|
|
391
|
+
// }
|
|
392
|
+
this.dbEventsManager = new DBEventsManager_1.DBEventsManager(db, pgp);
|
|
393
|
+
this.writeDBSchema();
|
|
394
|
+
/* 5. Finish init and provide DBO object */
|
|
395
|
+
try {
|
|
396
|
+
if (this.destroyed) {
|
|
397
|
+
console.trace(1);
|
|
398
|
+
}
|
|
399
|
+
onReady(this.dbo, this.db);
|
|
400
|
+
}
|
|
401
|
+
catch (err) {
|
|
402
|
+
console.error("Prostgles: Error within onReady: \n", err);
|
|
403
|
+
}
|
|
404
|
+
this.loaded = true;
|
|
405
|
+
return {
|
|
406
|
+
db: this.dbo,
|
|
407
|
+
_db: db,
|
|
408
|
+
pgp,
|
|
409
|
+
io: this.opts.io,
|
|
410
|
+
destroy: async () => {
|
|
411
|
+
console.log("destroying prgl instance");
|
|
412
|
+
this.destroyed = true;
|
|
413
|
+
if (this.opts.io) {
|
|
414
|
+
this.opts.io.on("connection", () => {
|
|
415
|
+
console.log("Socket connected to destroyed instance");
|
|
416
|
+
});
|
|
417
|
+
if (typeof this.opts.io.close === "function") {
|
|
418
|
+
this.opts.io.close();
|
|
419
|
+
console.log("this.io.close");
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
this.dboBuilder?.destroy();
|
|
423
|
+
this.dbo = undefined;
|
|
424
|
+
this.db = undefined;
|
|
425
|
+
await db.$pool.end();
|
|
426
|
+
await sleep(1000);
|
|
427
|
+
return true;
|
|
428
|
+
}
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
catch (e) {
|
|
432
|
+
console.trace(e);
|
|
433
|
+
// @ts-ignore
|
|
434
|
+
throw "init issues: " + e.toString();
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
async runSQLFile(filePath) {
|
|
438
|
+
const fileContent = await this.getFileText(filePath); //.then(console.log);
|
|
439
|
+
return this.db?.multi(fileContent).then((data) => {
|
|
440
|
+
console.log("Prostgles: SQL file executed successfuly \n -> " + filePath);
|
|
441
|
+
return data;
|
|
442
|
+
}).catch((err) => {
|
|
443
|
+
const { position, length } = err, lines = fileContent.split("\n");
|
|
444
|
+
let errMsg = filePath + " error: ";
|
|
445
|
+
if (position && length && fileContent) {
|
|
446
|
+
const startLine = Math.max(0, fileContent.substring(0, position).split("\n").length - 2), endLine = startLine + 3;
|
|
447
|
+
errMsg += "\n\n";
|
|
448
|
+
errMsg += lines.slice(startLine, endLine).map((txt, i) => `${startLine + i + 1} ${i === 1 ? "->" : " "} ${txt}`).join("\n");
|
|
449
|
+
errMsg += "\n\n";
|
|
450
|
+
}
|
|
451
|
+
console.error(errMsg, err);
|
|
452
|
+
throw err;
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
async setSocketEvents() {
|
|
456
|
+
this.checkDb();
|
|
457
|
+
if (!this.dbo)
|
|
458
|
+
throw "dbo missing";
|
|
459
|
+
let publishParser = new PublishParser_1.PublishParser(this.opts.publish, this.opts.publishMethods, this.opts.publishRawSQL, this.dbo, this.db, this);
|
|
460
|
+
this.publishParser = publishParser;
|
|
461
|
+
if (!this.opts.io)
|
|
462
|
+
return;
|
|
463
|
+
/* Already initialised. Only reconnect sockets */
|
|
464
|
+
if (this.connectedSockets.length) {
|
|
465
|
+
this.connectedSockets.forEach((s) => {
|
|
466
|
+
s.emit(prostgles_types_1.CHANNELS.SCHEMA_CHANGED);
|
|
467
|
+
this.pushSocketSchema(s);
|
|
468
|
+
});
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
/* Initialise */
|
|
472
|
+
this.opts.io.on('connection', async (socket) => {
|
|
473
|
+
if (this.destroyed) {
|
|
474
|
+
console.log("Socket connected to destroyed instance");
|
|
475
|
+
socket.disconnect();
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
this.connectedSockets.push(socket);
|
|
479
|
+
if (!this.db || !this.dbo)
|
|
480
|
+
throw "db/dbo missing";
|
|
481
|
+
let { dbo, db, pgp } = this;
|
|
482
|
+
try {
|
|
483
|
+
if (this.opts.onSocketConnect)
|
|
484
|
+
await this.opts.onSocketConnect(socket, dbo, db);
|
|
485
|
+
/* RUN Client request from Publish.
|
|
486
|
+
Checks request against publish and if OK run it with relevant publish functions. Local (server) requests do not check the policy
|
|
487
|
+
*/
|
|
488
|
+
socket.removeAllListeners(prostgles_types_1.CHANNELS.DEFAULT);
|
|
489
|
+
socket.on(prostgles_types_1.CHANNELS.DEFAULT, async ({ tableName, command, param1, param2, param3 }, cb = (...callback) => { }) => {
|
|
490
|
+
try { /* Channel name will only include client-sent params so we ignore table_rules enforced params */
|
|
491
|
+
if (!socket || !this.authHandler || !this.publishParser || !this.dbo) {
|
|
492
|
+
console.error("socket or authhandler missing??!!");
|
|
493
|
+
throw "socket or authhandler missing??!!";
|
|
494
|
+
}
|
|
495
|
+
const clientInfo = await this.authHandler.getClientInfo({ socket });
|
|
496
|
+
let valid_table_command_rules = await this.publishParser.getValidatedRequestRule({ tableName, command, localParams: { socket } }, clientInfo);
|
|
497
|
+
if (valid_table_command_rules) {
|
|
498
|
+
//@ts-ignore
|
|
499
|
+
let res = await this.dbo[tableName][command](param1, param2, param3, valid_table_command_rules, { socket, has_rules: true });
|
|
500
|
+
cb(null, res);
|
|
501
|
+
}
|
|
502
|
+
else
|
|
503
|
+
throw `Invalid OR disallowed request: ${tableName}.${command} `;
|
|
504
|
+
}
|
|
505
|
+
catch (err) {
|
|
506
|
+
// const _err_msg = err.toString();
|
|
507
|
+
// cb({ msg: _err_msg, err });
|
|
508
|
+
console.trace(err);
|
|
509
|
+
cb(err);
|
|
510
|
+
// console.warn("runPublishedRequest ERROR: ", err, socket._user);
|
|
511
|
+
}
|
|
512
|
+
});
|
|
513
|
+
socket.on("disconnect", () => {
|
|
514
|
+
this.dbEventsManager?.removeNotice(socket);
|
|
515
|
+
this.dbEventsManager?.removeNotify(undefined, socket);
|
|
516
|
+
this.connectedSockets = this.connectedSockets.filter(s => s.id !== socket.id);
|
|
517
|
+
// subscriptions = subscriptions.filter(sub => sub.socket.id !== socket.id);
|
|
518
|
+
if (this.opts.onSocketDisconnect) {
|
|
519
|
+
this.opts.onSocketDisconnect(socket, dbo);
|
|
520
|
+
}
|
|
521
|
+
;
|
|
522
|
+
});
|
|
523
|
+
socket.removeAllListeners(prostgles_types_1.CHANNELS.METHOD);
|
|
524
|
+
socket.on(prostgles_types_1.CHANNELS.METHOD, async ({ method, params }, cb = (...callback) => { }) => {
|
|
525
|
+
try {
|
|
526
|
+
const methods = await this.publishParser?.getMethods(socket);
|
|
527
|
+
if (!methods || !methods[method]) {
|
|
528
|
+
cb("Disallowed/missing method " + JSON.stringify(method));
|
|
529
|
+
}
|
|
530
|
+
else {
|
|
531
|
+
try {
|
|
532
|
+
const res = await methods[method](...params);
|
|
533
|
+
cb(null, res);
|
|
534
|
+
}
|
|
535
|
+
catch (err) {
|
|
536
|
+
makeSocketError(cb, err);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
catch (err) {
|
|
541
|
+
makeSocketError(cb, err);
|
|
542
|
+
console.warn("method ERROR: ", err, socket._user);
|
|
543
|
+
}
|
|
544
|
+
});
|
|
545
|
+
this.pushSocketSchema(socket);
|
|
546
|
+
}
|
|
547
|
+
catch (e) {
|
|
548
|
+
console.trace("setSocketEvents: ", e);
|
|
549
|
+
}
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
exports.Prostgles = Prostgles;
|
|
554
|
+
function makeSocketError(cb, err) {
|
|
555
|
+
const err_msg = (err instanceof Error) ?
|
|
556
|
+
err.toString() :
|
|
557
|
+
(0, DboBuilder_1.isPlainObject)(err) ?
|
|
558
|
+
JSON.stringify(err, null, 2) :
|
|
559
|
+
err.toString(), e = { err_msg, err };
|
|
560
|
+
cb(e);
|
|
561
|
+
}
|
|
562
|
+
// const ALL_PUBLISH_METHODS = ["update", "upsert", "delete", "insert", "find", "findOne", "subscribe", "unsubscribe", "sync", "unsync", "remove"];
|
|
563
|
+
// const ALL_PUBLISH_METHODS = RULE_TO_METHODS.map(r => r.methods).flat();
|
|
564
|
+
// export function flat(arr){
|
|
565
|
+
// // let res = arr.reduce((acc, val) => [ ...acc, ...val ], []);
|
|
566
|
+
// let res = arr.reduce(function (farr, toFlatten) {
|
|
567
|
+
// return farr.concat(Array.isArray(toFlatten) ? flat(toFlatten) : toFlatten);
|
|
568
|
+
// }, []);
|
|
569
|
+
// return res;
|
|
570
|
+
// }
|
|
571
|
+
async function isSuperUser(db) {
|
|
572
|
+
return db.oneOrNone("select usesuper from pg_user where usename = CURRENT_USER;").then(r => r.usesuper);
|
|
573
|
+
}
|
|
574
|
+
exports.isSuperUser = isSuperUser;
|
|
575
|
+
function sleep(ms) {
|
|
576
|
+
return new Promise((resolve) => {
|
|
577
|
+
setTimeout(resolve, ms);
|
|
578
|
+
});
|
|
579
|
+
}
|
package/lib/Prostgles.ts
CHANGED
|
@@ -165,7 +165,7 @@ export type FileTableConfig = {
|
|
|
165
165
|
imageOptions?: ImageOptions
|
|
166
166
|
};
|
|
167
167
|
|
|
168
|
-
export type ProstglesInitOptions<S
|
|
168
|
+
export type ProstglesInitOptions<S = void> = {
|
|
169
169
|
dbConnection: DbConnection;
|
|
170
170
|
dbOptions?: DbConnectionOpts;
|
|
171
171
|
tsGeneratedTypesDir?: string;
|
|
@@ -246,9 +246,9 @@ const DEFAULT_KEYWORDS = {
|
|
|
246
246
|
|
|
247
247
|
import * as fs from 'fs';
|
|
248
248
|
import { DBOFullyTyped } from "./DBSchemaBuilder";
|
|
249
|
-
export class Prostgles
|
|
249
|
+
export class Prostgles {
|
|
250
250
|
|
|
251
|
-
opts: ProstglesInitOptions
|
|
251
|
+
opts: ProstglesInitOptions = {
|
|
252
252
|
DEBUG_MODE: false,
|
|
253
253
|
dbConnection: {
|
|
254
254
|
host: "localhost",
|
|
@@ -280,7 +280,7 @@ export class Prostgles<S extends DBSchema | undefined = undefined> {
|
|
|
280
280
|
}
|
|
281
281
|
publishParser?: PublishParser;
|
|
282
282
|
|
|
283
|
-
authHandler?: AuthHandler
|
|
283
|
+
authHandler?: AuthHandler;
|
|
284
284
|
|
|
285
285
|
|
|
286
286
|
keywords = DEFAULT_KEYWORDS;
|
|
@@ -409,8 +409,8 @@ export class Prostgles<S extends DBSchema | undefined = undefined> {
|
|
|
409
409
|
|
|
410
410
|
isSuperUser = false;
|
|
411
411
|
schema_checkIntervalMillis: any;
|
|
412
|
-
async init(onReady: (dbo: DBOFullyTyped
|
|
413
|
-
db: DBOFullyTyped
|
|
412
|
+
async init(onReady: (dbo: DBOFullyTyped, db: DB) => any): Promise<{
|
|
413
|
+
db: DBOFullyTyped;
|
|
414
414
|
_db: DB;
|
|
415
415
|
pgp: PGP;
|
|
416
416
|
io?: any;
|