prostgles-server 2.0.185 → 2.0.188
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/DboBuilder/insert.d.ts +5 -0
- package/dist/DboBuilder/insert.d.ts.map +1 -0
- package/dist/DboBuilder/insert.js +129 -0
- package/dist/DboBuilder/insert.js.map +1 -0
- package/dist/DboBuilder/insertDataParse.d.ts +11 -0
- package/dist/DboBuilder/insertDataParse.d.ts.map +1 -0
- package/dist/DboBuilder/insertDataParse.js +283 -0
- package/dist/DboBuilder/insertDataParse.js.map +1 -0
- package/dist/DboBuilder.d.ts +10 -5
- package/dist/DboBuilder.d.ts.map +1 -1
- package/dist/DboBuilder.js +121 -374
- package/dist/DboBuilder.js.map +1 -1
- package/dist/FileManager.d.ts.map +1 -1
- package/dist/FileManager.js +17 -12
- package/dist/FileManager.js.map +1 -1
- package/dist/Prostgles.d.ts +16 -14
- package/dist/Prostgles.d.ts.map +1 -1
- package/dist/Prostgles.js +7 -7
- package/dist/Prostgles.js.map +1 -1
- package/dist/PubSubManager.d.ts +1 -0
- package/dist/PubSubManager.d.ts.map +1 -1
- package/dist/PubSubManager.js +2 -1
- package/dist/PubSubManager.js.map +1 -1
- package/dist/QueryBuilder.d.ts +7 -3
- package/dist/QueryBuilder.d.ts.map +1 -1
- package/dist/QueryBuilder.js +7 -2
- package/dist/QueryBuilder.js.map +1 -1
- package/dist/TableConfig.d.ts +1 -4
- package/dist/TableConfig.d.ts.map +1 -1
- package/dist/TableConfig.js +16 -1
- package/dist/TableConfig.js.map +1 -1
- package/lib/DboBuilder/insert.d.ts +5 -0
- package/lib/DboBuilder/insert.d.ts.map +1 -0
- package/lib/DboBuilder/insert.js +128 -0
- package/lib/DboBuilder/insert.ts +138 -0
- package/lib/DboBuilder/insertDataParse.d.ts +11 -0
- package/lib/DboBuilder/insertDataParse.d.ts.map +1 -0
- package/lib/DboBuilder/insertDataParse.js +282 -0
- package/lib/DboBuilder/insertDataParse.ts +355 -0
- package/lib/DboBuilder.d.ts +10 -5
- package/lib/DboBuilder.d.ts.map +1 -1
- package/lib/DboBuilder.js +121 -374
- package/lib/DboBuilder.ts +138 -453
- package/lib/FileManager.d.ts.map +1 -1
- package/lib/FileManager.js +17 -12
- package/lib/FileManager.ts +18 -13
- package/lib/Prostgles.d.ts +16 -14
- package/lib/Prostgles.d.ts.map +1 -1
- package/lib/Prostgles.js +7 -7
- package/lib/Prostgles.ts +664 -650
- package/lib/PubSubManager.d.ts +1 -0
- package/lib/PubSubManager.d.ts.map +1 -1
- package/lib/PubSubManager.js +2 -1
- package/lib/PubSubManager.ts +3 -1
- package/lib/QueryBuilder.d.ts.map +1 -1
- package/lib/QueryBuilder.js +7 -2
- package/lib/QueryBuilder.ts +12 -7
- package/lib/SchemaWatchManager.ts +72 -0
- package/lib/TableConfig.d.ts +1 -4
- package/lib/TableConfig.d.ts.map +1 -1
- package/lib/TableConfig.js +16 -1
- package/lib/TableConfig.ts +21 -8
- package/package.json +3 -3
- package/tests/client/PID.txt +1 -1
- package/tests/client/package-lock.json +15 -15
- package/tests/client/package.json +1 -1
- package/tests/client/tsconfig.json +1 -1
- package/tests/client_only_queries.d.ts +1 -1
- package/tests/client_only_queries.d.ts.map +1 -1
- package/tests/client_only_queries.ts +1 -1
- package/tests/isomorphic_queries.d.ts +1 -1
- package/tests/isomorphic_queries.d.ts.map +1 -1
- package/tests/isomorphic_queries.js +49 -1
- package/tests/isomorphic_queries.ts +66 -4
- package/tests/manual_test/DBoGenerated.d.ts +398 -0
- package/tests/manual_test/index.d.ts +2 -0
- package/tests/manual_test/index.d.ts.map +1 -0
- package/tests/{config_test2 → manual_test}/index.html +14 -23
- package/tests/{config_test2 → manual_test}/index.js +21 -15
- package/tests/{config_test2 → manual_test}/index.ts +22 -17
- package/tests/{config_test2 → manual_test}/init.sql +36 -5
- package/tests/manual_test/package-lock.json +2483 -0
- package/tests/{config_test2 → manual_test}/package.json +6 -7
- package/tests/manual_test/tsconfig.json +21 -0
- package/tests/server/DBoGenerated.d.ts +70 -0
- package/tests/server/index.js +29 -2
- package/tests/server/index.ts +30 -4
- package/tests/server/init.sql +25 -0
- package/tests/server/package-lock.json +5 -5
- package/tests/config_test2/DBoGenerated.d.ts +0 -135
- package/tests/config_test2/tsconfig.json +0 -21
package/lib/Prostgles.ts
CHANGED
|
@@ -30,70 +30,70 @@ type DbConnection = string | pg.IConnectionParameters<pg.IClient>;
|
|
|
30
30
|
type DbConnectionOpts = pg.IDefaults;
|
|
31
31
|
export const TABLE_METHODS = ["update", "find", "findOne", "insert", "delete", "upsert"] as const;
|
|
32
32
|
function getDbConnection(dbConnection: DbConnection, options: DbConnectionOpts | undefined, debugQueries = false, onNotice: ProstglesInitOptions["onNotice"]): { db: DB, pgp: PGP } {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
33
|
+
let pgp: PGP = pgPromise({
|
|
34
|
+
|
|
35
|
+
promiseLib: promise,
|
|
36
|
+
...(debugQueries ? {
|
|
37
|
+
query: function (e) {
|
|
38
|
+
console.log({ psql: e.query, params: e.params });
|
|
39
|
+
},
|
|
40
|
+
} : {}),
|
|
41
|
+
...((onNotice || debugQueries) ? {
|
|
42
|
+
connect: function (client, dc, isFresh) {
|
|
43
|
+
if (isFresh && !client.listeners('notice').length) {
|
|
44
|
+
client.on('notice', function (msg) {
|
|
45
|
+
if (onNotice) {
|
|
46
|
+
onNotice(msg, get(msg, "message"));
|
|
47
|
+
} else {
|
|
48
|
+
console.log("notice: %j", get(msg, "message"));
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
if (isFresh && !client.listeners('error').length) {
|
|
53
|
+
client.on('error', function (msg) {
|
|
54
|
+
if (onNotice) {
|
|
55
|
+
onNotice(msg, get(msg, "message"));
|
|
56
|
+
} else {
|
|
57
|
+
console.log("error: %j", get(msg, "message"));
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
} : {})
|
|
63
|
+
});
|
|
64
|
+
pgp.pg.defaults.max = 70;
|
|
65
65
|
|
|
66
|
-
|
|
67
|
-
|
|
66
|
+
// /* Casts count/sum/max to bigint. Needs rework to remove casting "+count" and other issues; */
|
|
67
|
+
// pgp.pg.types.setTypeParser(20, BigInt);
|
|
68
68
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
69
|
+
if (options) {
|
|
70
|
+
Object.assign(pgp.pg.defaults, options);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
db: pgp(dbConnection),
|
|
75
|
+
pgp
|
|
76
|
+
};
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
export const JOIN_TYPES = ["one-many", "many-one", "one-one", "many-many"] as const;
|
|
80
80
|
export type Join = {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
81
|
+
tables: [string, string];
|
|
82
|
+
on: { [key: string]: string }[]; // Allow multi references to table
|
|
83
|
+
type: typeof JOIN_TYPES[number];
|
|
84
84
|
};
|
|
85
85
|
export type Joins = Join[] | "inferred";
|
|
86
86
|
|
|
87
87
|
|
|
88
88
|
|
|
89
89
|
type Keywords = {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
90
|
+
$and: string;
|
|
91
|
+
$or: string;
|
|
92
|
+
$not: string;
|
|
93
93
|
};
|
|
94
94
|
|
|
95
95
|
export type DeepPartial<T> = {
|
|
96
|
-
|
|
96
|
+
[P in keyof T]?: DeepPartial<T[P]>;
|
|
97
97
|
};
|
|
98
98
|
// export type I18N_CONFIG<LANG_IDS = { en: 1, fr: 1 }> = {
|
|
99
99
|
// fallbackLang: keyof LANG_IDS;
|
|
@@ -107,23 +107,23 @@ export type DeepPartial<T> = {
|
|
|
107
107
|
// }
|
|
108
108
|
|
|
109
109
|
type ExpressApp = {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
) => any
|
|
110
|
+
get: (
|
|
111
|
+
routePath: string,
|
|
112
|
+
cb: (
|
|
113
|
+
req: {
|
|
114
|
+
params: { name: string },
|
|
115
|
+
cookies: { sid: string }
|
|
116
|
+
},
|
|
117
|
+
res: {
|
|
118
|
+
redirect: (redirectUrl: string) => any;
|
|
119
|
+
contentType: (type: string) => void;
|
|
120
|
+
sendFile: (fileName: string, opts?: { root: string }) => any;
|
|
121
|
+
status: (code: number) => {
|
|
122
|
+
json: (response: AnyObject) => any;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
126
125
|
) => any
|
|
126
|
+
) => any
|
|
127
127
|
};
|
|
128
128
|
|
|
129
129
|
/**
|
|
@@ -143,85 +143,99 @@ type ExpressApp = {
|
|
|
143
143
|
* 2. create a lookup table lookup_media_{referencedTable} that joins referencedTable to the media table
|
|
144
144
|
*/
|
|
145
145
|
export type FileTableConfig = {
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
146
|
+
tableName?: string; /* defaults to 'media' */
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* GET path used in serving media. defaults to /${tableName}
|
|
150
|
+
*/
|
|
151
|
+
fileServeRoute?: string;
|
|
152
|
+
|
|
153
|
+
awsS3Config?: S3Config;
|
|
154
|
+
localConfig?: LocalConfig;
|
|
155
|
+
// {
|
|
156
|
+
// region: string;
|
|
157
|
+
// bucket: string;
|
|
158
|
+
// accessKeyId: string;
|
|
159
|
+
// secretAccessKey: string;
|
|
160
|
+
// },
|
|
161
|
+
expressApp: ExpressApp;
|
|
162
|
+
referencedTables?: { // minFiles: number; maxFiles: number;
|
|
163
|
+
[tableName: string]:
|
|
164
|
+
| "one" | "many"
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* If defined then will try to create (if necessary) a column in the files table which will reference this table's primary key (must have one)
|
|
168
|
+
* */
|
|
169
|
+
// | { type: "lookup_table"; minFilesPerRow: number; maxFilesPerRow: number; maxFileSizeMB: number; }
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* If defined then will try to create (if necessary) this column which will reference files_table(id)
|
|
173
|
+
* Prostgles UI will use these hints (obtained through tableHandler.getInfo())
|
|
174
|
+
* */
|
|
175
|
+
// | { type: "column", referenceColumns: Record<string, { maxFileSizeMB: number; }> }
|
|
176
|
+
},
|
|
177
|
+
imageOptions?: ImageOptions
|
|
166
178
|
};
|
|
167
179
|
|
|
168
180
|
export type ProstglesInitOptions<S = void> = {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
181
|
+
dbConnection: DbConnection;
|
|
182
|
+
dbOptions?: DbConnectionOpts;
|
|
183
|
+
tsGeneratedTypesDir?: string;
|
|
184
|
+
io?: any;
|
|
185
|
+
publish?: Publish<S>;
|
|
186
|
+
publishMethods?: PublishMethods<S>;
|
|
187
|
+
publishRawSQL?(params: PublishParams<S>): ((boolean | "*") | Promise<(boolean | "*")>);
|
|
188
|
+
joins?: Joins;
|
|
189
|
+
schema?: string;
|
|
190
|
+
sqlFilePath?: string;
|
|
191
|
+
onReady(dbo: DBOFullyTyped<S>, db: DB): void;
|
|
192
|
+
transactions?: string | boolean;
|
|
193
|
+
wsChannelNamePrefix?: string;
|
|
194
|
+
onSocketConnect?(socket: PRGLIOSocket, dbo: DBOFullyTyped<S>, db?: DB): any;
|
|
195
|
+
onSocketDisconnect?(socket: PRGLIOSocket, dbo: DBOFullyTyped<S>, db?: DB): any;
|
|
196
|
+
auth?: Auth<S>;
|
|
197
|
+
DEBUG_MODE?: boolean;
|
|
198
|
+
watchSchemaType?:
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Will set database event trigger for schema changes. Requires superuser
|
|
202
|
+
* Default
|
|
203
|
+
*/
|
|
204
|
+
| "DDL_trigger"
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Will check client queries for schema changes
|
|
208
|
+
* fallback if DDL not possible
|
|
209
|
+
*/
|
|
210
|
+
| "prostgles_queries"
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Schema checked for changes every 'checkIntervalMillis" milliseconds
|
|
214
|
+
*/
|
|
215
|
+
| { checkIntervalMillis: number };
|
|
216
|
+
|
|
217
|
+
watchSchema?:
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* If true then DBoGenerated.d.ts will be updated and "onReady" will be called with new schema on both client and server
|
|
221
|
+
*/
|
|
222
|
+
| boolean
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Will only rewrite the DBoGenerated.d.ts found in tsGeneratedTypesDir
|
|
226
|
+
* This is meant to be used in development when server restarts on file change
|
|
227
|
+
*/
|
|
228
|
+
| "hotReloadMode"
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Function called when schema changes. Nothing else triggered
|
|
232
|
+
*/
|
|
233
|
+
| ((event: { command: string; query: string }) => void)
|
|
234
|
+
|
|
235
|
+
keywords?: Keywords;
|
|
236
|
+
onNotice?: (notice: AnyObject, message?: string) => void;
|
|
237
|
+
fileTable?: FileTableConfig;
|
|
238
|
+
tableConfig?: TableConfig;
|
|
225
239
|
}
|
|
226
240
|
|
|
227
241
|
/*
|
|
@@ -233,572 +247,572 @@ export type ProstglesInitOptions<S = void> = {
|
|
|
233
247
|
*/
|
|
234
248
|
|
|
235
249
|
export type OnReady = {
|
|
236
|
-
|
|
237
|
-
|
|
250
|
+
dbo: DBHandlerServer;
|
|
251
|
+
db: DB;
|
|
238
252
|
}
|
|
239
253
|
|
|
240
254
|
const DEFAULT_KEYWORDS = {
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
255
|
+
$filter: "$filter",
|
|
256
|
+
$and: "$and",
|
|
257
|
+
$or: "$or",
|
|
258
|
+
$not: "$not"
|
|
245
259
|
};
|
|
246
260
|
|
|
247
261
|
import * as fs from 'fs';
|
|
248
262
|
import { DBOFullyTyped } from "./DBSchemaBuilder";
|
|
249
263
|
export class Prostgles {
|
|
250
264
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
265
|
+
opts: ProstglesInitOptions = {
|
|
266
|
+
DEBUG_MODE: false,
|
|
267
|
+
dbConnection: {
|
|
268
|
+
host: "localhost",
|
|
269
|
+
port: 5432,
|
|
270
|
+
application_name: "prostgles_app"
|
|
271
|
+
},
|
|
272
|
+
onReady: () => { },
|
|
273
|
+
schema: "public",
|
|
274
|
+
watchSchema: false,
|
|
275
|
+
watchSchemaType: "DDL_trigger",
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
// dbConnection: DbConnection = {
|
|
279
|
+
// host: "localhost",
|
|
280
|
+
// port: 5432,
|
|
281
|
+
// application_name: "prostgles_app"
|
|
282
|
+
// };
|
|
283
|
+
// dbOptions: DbConnectionOpts;
|
|
284
|
+
db?: DB;
|
|
285
|
+
pgp?: PGP;
|
|
286
|
+
dbo?: DBHandlerServer;
|
|
287
|
+
_dboBuilder?: DboBuilder;
|
|
288
|
+
get dboBuilder(): DboBuilder {
|
|
289
|
+
if (!this._dboBuilder) throw "get dboBuilder: it's undefined"
|
|
290
|
+
return this._dboBuilder;
|
|
291
|
+
}
|
|
292
|
+
set dboBuilder(d: DboBuilder) {
|
|
293
|
+
this._dboBuilder = d;
|
|
294
|
+
}
|
|
295
|
+
publishParser?: PublishParser;
|
|
296
|
+
|
|
297
|
+
authHandler?: AuthHandler;
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
keywords = DEFAULT_KEYWORDS;
|
|
301
|
+
private loaded = false;
|
|
302
|
+
|
|
303
|
+
dbEventsManager?: DBEventsManager;
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
fileManager?: FileManager;
|
|
307
|
+
|
|
308
|
+
tableConfigurator?: TableConfigurator;
|
|
309
|
+
|
|
310
|
+
isMedia(tableName: string) {
|
|
311
|
+
return this.opts?.fileTable?.tableName === tableName;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
constructor(params: ProstglesInitOptions) {
|
|
315
|
+
if (!params) throw "ProstglesInitOptions missing";
|
|
316
|
+
if (!params.io) console.warn("io missing. WebSockets will not be set up");
|
|
317
|
+
|
|
318
|
+
// TODO: find an exact keyof T<->arr TS matching method
|
|
319
|
+
let config: Array<keyof ProstglesInitOptions> = [
|
|
320
|
+
"transactions", "joins", "tsGeneratedTypesDir",
|
|
321
|
+
"onReady", "dbConnection", "dbOptions", "publishMethods", "io",
|
|
322
|
+
"publish", "schema", "publishRawSQL", "wsChannelNamePrefix", "onSocketConnect",
|
|
323
|
+
"onSocketDisconnect", "sqlFilePath", "auth", "DEBUG_MODE", "watchSchema", "watchSchemaType",
|
|
324
|
+
"fileTable", "tableConfig"
|
|
325
|
+
];
|
|
326
|
+
const unknownParams = Object.keys(params).filter((key: string) => !(config as string[]).includes(key))
|
|
327
|
+
if (unknownParams.length) {
|
|
328
|
+
console.error(`Unrecognised ProstglesInitOptions params: ${unknownParams.join()}`);
|
|
277
329
|
}
|
|
278
|
-
|
|
279
|
-
|
|
330
|
+
|
|
331
|
+
Object.assign(this.opts, params);
|
|
332
|
+
|
|
333
|
+
/* set defaults */
|
|
334
|
+
if (this.opts?.fileTable) {
|
|
335
|
+
this.opts.fileTable.tableName = this.opts?.fileTable?.tableName || "media";
|
|
280
336
|
}
|
|
281
|
-
|
|
337
|
+
this.opts.schema = this.opts.schema || "public";
|
|
282
338
|
|
|
283
|
-
|
|
339
|
+
this.keywords = {
|
|
340
|
+
...DEFAULT_KEYWORDS,
|
|
341
|
+
...params.keywords,
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
destroyed = false;
|
|
346
|
+
|
|
347
|
+
async onSchemaChange(event: { command: string; query: string }) {
|
|
348
|
+
const { watchSchema, watchSchemaType, onReady, tsGeneratedTypesDir } = this.opts;
|
|
349
|
+
if (watchSchema && this.loaded) {
|
|
350
|
+
console.log("Schema changed");
|
|
351
|
+
const { query } = event;
|
|
352
|
+
if (typeof query === "string" && query.includes(PubSubManager.EXCLUDE_QUERY_FROM_SCHEMA_WATCH_ID)) {
|
|
353
|
+
console.log("Schema change event excluded from triggers due to EXCLUDE_QUERY_FROM_SCHEMA_WATCH_ID");
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
if (typeof watchSchema === "function") {
|
|
358
|
+
/* Only call the provided func */
|
|
359
|
+
watchSchema(event);
|
|
360
|
+
|
|
361
|
+
} else if (watchSchema === "hotReloadMode") {
|
|
362
|
+
if (tsGeneratedTypesDir) {
|
|
363
|
+
/* Hot reload integration. Will only touch tsGeneratedTypesDir */
|
|
364
|
+
console.log("watchSchema: Re-writing TS schema");
|
|
365
|
+
|
|
366
|
+
await this.refreshDBO();
|
|
367
|
+
this.writeDBSchema(true);
|
|
368
|
+
}
|
|
284
369
|
|
|
370
|
+
} else if (watchSchema === true || isPlainObject(watchSchemaType) && "checkIntervalMillis" in watchSchemaType) {
|
|
371
|
+
/* Full re-init. Sockets must reconnect */
|
|
372
|
+
console.log("watchSchema: Full re-initialisation")
|
|
373
|
+
this.init(onReady);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
checkDb() {
|
|
379
|
+
if (!this.db || !this.db.connect) throw "something went wrong getting a db connection";
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
getTSFileName() {
|
|
383
|
+
const fileName = "DBoGenerated.d.ts" //`dbo_${this.schema}_types.ts`;
|
|
384
|
+
const fullPath = (this.opts.tsGeneratedTypesDir || "") + fileName;
|
|
385
|
+
return { fileName, fullPath }
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
private getFileText(fullPath: string, format = "utf8"): Promise<string> {
|
|
389
|
+
return new Promise((resolve, reject) => {
|
|
390
|
+
fs.readFile(fullPath, 'utf8', function (err, data) {
|
|
391
|
+
if (err) reject(err);
|
|
392
|
+
else resolve(data);
|
|
393
|
+
});
|
|
394
|
+
})
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
writeDBSchema(force = false) {
|
|
398
|
+
|
|
399
|
+
if (this.opts.tsGeneratedTypesDir) {
|
|
400
|
+
const { fullPath, fileName } = this.getTSFileName();
|
|
401
|
+
const header = `/* This file was generated by Prostgles \n` +
|
|
402
|
+
// `* ${(new Date).toUTCString()} \n`
|
|
403
|
+
`*/ \n\n `;
|
|
404
|
+
const fileContent = header + this.dboBuilder.tsTypesDefinition;
|
|
405
|
+
fs.readFile(fullPath, 'utf8', function (err, data) {
|
|
406
|
+
if (err || (force || data !== fileContent)) {
|
|
407
|
+
fs.writeFileSync(fullPath, fileContent);
|
|
408
|
+
console.log("Prostgles: Created typescript schema definition file: \n " + fileName)
|
|
409
|
+
}
|
|
410
|
+
});
|
|
411
|
+
} else if (force) {
|
|
412
|
+
console.error("Schema changed. tsGeneratedTypesDir needs to be set to reload server")
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
refreshDBO = async () => {
|
|
417
|
+
if (this._dboBuilder) this._dboBuilder.destroy();
|
|
418
|
+
this.dboBuilder = await DboBuilder.create(this as any) as any;
|
|
419
|
+
if (!this.dboBuilder) throw "this.dboBuilder"
|
|
420
|
+
this.dbo = this.dboBuilder.dbo as any;
|
|
421
|
+
return this.dbo;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
isSuperUser = false;
|
|
425
|
+
schema_checkIntervalMillis?: NodeJS.Timeout;
|
|
426
|
+
async init(onReady: (dbo: DBOFullyTyped, db: DB) => any): Promise<{
|
|
427
|
+
db: DBOFullyTyped;
|
|
428
|
+
_db: DB;
|
|
429
|
+
pgp: PGP;
|
|
430
|
+
io?: any;
|
|
431
|
+
destroy: () => Promise<boolean>;
|
|
432
|
+
}> {
|
|
433
|
+
this.loaded = false;
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
if (this.opts.watchSchema === "hotReloadMode" && !this.opts.tsGeneratedTypesDir) {
|
|
437
|
+
throw "tsGeneratedTypesDir option is needed for watchSchema: hotReloadMode to work ";
|
|
438
|
+
} else if (
|
|
439
|
+
this.opts.watchSchema &&
|
|
440
|
+
typeof this.opts.watchSchemaType === "object" &&
|
|
441
|
+
"checkIntervalMillis" in this.opts.watchSchemaType &&
|
|
442
|
+
typeof this.opts.watchSchemaType.checkIntervalMillis === "number"
|
|
443
|
+
) {
|
|
444
|
+
|
|
445
|
+
if (this.schema_checkIntervalMillis) {
|
|
446
|
+
clearInterval(this.schema_checkIntervalMillis);
|
|
447
|
+
this.schema_checkIntervalMillis = setInterval(async () => {
|
|
448
|
+
const dbuilder = await DboBuilder.create(this as any);
|
|
449
|
+
if (dbuilder.tsTypesDefinition !== this.dboBuilder.tsTypesDefinition) {
|
|
450
|
+
this.refreshDBO();
|
|
451
|
+
this.init(onReady);
|
|
452
|
+
}
|
|
453
|
+
}, this.opts.watchSchemaType.checkIntervalMillis)
|
|
454
|
+
}
|
|
455
|
+
}
|
|
285
456
|
|
|
286
|
-
|
|
287
|
-
|
|
457
|
+
/* 1. Connect to db */
|
|
458
|
+
if (!this.db) {
|
|
459
|
+
const { db, pgp } = getDbConnection(this.opts.dbConnection, this.opts.dbOptions, this.opts.DEBUG_MODE,
|
|
460
|
+
notice => {
|
|
461
|
+
if (this.opts.onNotice) this.opts.onNotice(notice);
|
|
462
|
+
if (this.dbEventsManager) {
|
|
463
|
+
this.dbEventsManager.onNotice(notice)
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
);
|
|
467
|
+
this.db = db;
|
|
468
|
+
this.pgp = pgp;
|
|
469
|
+
this.isSuperUser = await isSuperUser(db);
|
|
470
|
+
}
|
|
471
|
+
this.checkDb();
|
|
472
|
+
const db = this.db!;
|
|
473
|
+
const pgp = this.pgp!;
|
|
288
474
|
|
|
289
|
-
|
|
475
|
+
/* 2. Execute any SQL file if provided */
|
|
476
|
+
if (this.opts.sqlFilePath) {
|
|
477
|
+
await this.runSQLFile(this.opts.sqlFilePath);
|
|
478
|
+
}
|
|
290
479
|
|
|
480
|
+
try {
|
|
291
481
|
|
|
292
|
-
|
|
482
|
+
await this.refreshDBO();
|
|
483
|
+
if (this.opts.tableConfig) {
|
|
484
|
+
this.tableConfigurator = new TableConfigurator(this as any);
|
|
485
|
+
try {
|
|
486
|
+
await this.tableConfigurator.init();
|
|
487
|
+
} catch (e) {
|
|
488
|
+
console.error("TableConfigurator: ", e);
|
|
489
|
+
throw e;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
293
492
|
|
|
294
|
-
|
|
493
|
+
/* 3. Make DBO object from all tables and views */
|
|
494
|
+
await this.refreshDBO();
|
|
295
495
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
496
|
+
/* Create media table if required */
|
|
497
|
+
if (this.opts.fileTable) {
|
|
498
|
+
const { awsS3Config, localConfig, imageOptions } = this.opts.fileTable;
|
|
499
|
+
await this.refreshDBO();
|
|
500
|
+
if (!awsS3Config && !localConfig) throw "fileTable missing param: Must provide awsS3Config OR localConfig";
|
|
501
|
+
//@ts-ignore
|
|
502
|
+
this.fileManager = new FileManager(awsS3Config || localConfig, imageOptions);
|
|
299
503
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
let config: Array<keyof ProstglesInitOptions> = [
|
|
306
|
-
"transactions", "joins", "tsGeneratedTypesDir",
|
|
307
|
-
"onReady", "dbConnection", "dbOptions", "publishMethods", "io",
|
|
308
|
-
"publish", "schema", "publishRawSQL", "wsChannelNamePrefix", "onSocketConnect",
|
|
309
|
-
"onSocketDisconnect", "sqlFilePath", "auth", "DEBUG_MODE", "watchSchema", "watchSchemaType",
|
|
310
|
-
"fileTable", "tableConfig"
|
|
311
|
-
];
|
|
312
|
-
const unknownParams = Object.keys(params).filter((key: string) => !(config as string[]).includes(key))
|
|
313
|
-
if(unknownParams.length){
|
|
314
|
-
console.error(`Unrecognised ProstglesInitOptions params: ${unknownParams.join()}`);
|
|
504
|
+
try {
|
|
505
|
+
await this.fileManager.init(this as any);
|
|
506
|
+
} catch (e) {
|
|
507
|
+
console.error("FileManager: ", e);
|
|
508
|
+
throw e;
|
|
315
509
|
}
|
|
316
|
-
|
|
317
|
-
|
|
510
|
+
}
|
|
511
|
+
await this.refreshDBO();
|
|
318
512
|
|
|
319
|
-
/* set defaults */
|
|
320
|
-
if(this.opts?.fileTable){
|
|
321
|
-
this.opts.fileTable.tableName = this.opts?.fileTable?.tableName || "media";
|
|
322
|
-
}
|
|
323
|
-
this.opts.schema = this.opts.schema || "public";
|
|
324
513
|
|
|
325
|
-
|
|
326
|
-
...DEFAULT_KEYWORDS,
|
|
327
|
-
...params.keywords,
|
|
328
|
-
}
|
|
329
|
-
}
|
|
514
|
+
if (this.opts.publish) {
|
|
330
515
|
|
|
331
|
-
|
|
516
|
+
if (!this.opts.io) console.warn("IO missing. Publish has no effect without io");
|
|
332
517
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
console.log("Schema changed");
|
|
337
|
-
const { query } = event;
|
|
338
|
-
if(typeof query === "string" && query.includes(PubSubManager.EXCLUDE_QUERY_FROM_SCHEMA_WATCH_ID)){
|
|
339
|
-
console.log("Schema change event excluded from triggers due to EXCLUDE_QUERY_FROM_SCHEMA_WATCH_ID");
|
|
340
|
-
return;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
if(typeof watchSchema === "function"){
|
|
344
|
-
/* Only call the provided func */
|
|
345
|
-
watchSchema(event);
|
|
346
|
-
|
|
347
|
-
} else if(watchSchema === "hotReloadMode") {
|
|
348
|
-
if(tsGeneratedTypesDir) {
|
|
349
|
-
/* Hot reload integration. Will only touch tsGeneratedTypesDir */
|
|
350
|
-
console.log("watchSchema: Re-writing TS schema");
|
|
351
|
-
|
|
352
|
-
await this.refreshDBO();
|
|
353
|
-
this.writeDBSchema(true);
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
} else if(watchSchema === true || "checkIntervalMillis" in watchSchema){
|
|
357
|
-
/* Full re-init. Sockets must reconnect */
|
|
358
|
-
console.log("watchSchema: Full re-initialisation")
|
|
359
|
-
this.init(onReady);
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
}
|
|
518
|
+
/* 3.9 Check auth config */
|
|
519
|
+
this.authHandler = new AuthHandler(this as any);
|
|
520
|
+
await this.authHandler.init();
|
|
363
521
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
}
|
|
522
|
+
this.publishParser = new PublishParser(this.opts.publish, this.opts.publishMethods, this.opts.publishRawSQL, this.dbo!, this.db, this as any);
|
|
523
|
+
this.dboBuilder.publishParser = this.publishParser;
|
|
367
524
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
const fullPath = (this.opts.tsGeneratedTypesDir || "") + fileName;
|
|
371
|
-
return { fileName, fullPath }
|
|
372
|
-
}
|
|
525
|
+
/* 4. Set publish and auth listeners */
|
|
526
|
+
await this.setSocketEvents();
|
|
373
527
|
|
|
374
|
-
|
|
375
|
-
return new Promise((resolve, reject) => {
|
|
376
|
-
fs.readFile(fullPath, 'utf8', function(err, data) {
|
|
377
|
-
if(err) reject(err);
|
|
378
|
-
else resolve(data);
|
|
379
|
-
});
|
|
380
|
-
})
|
|
381
|
-
}
|
|
528
|
+
} else if (this.opts.auth) throw "Auth config does not work without publish";
|
|
382
529
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
// `* ${(new Date).toUTCString()} \n`
|
|
389
|
-
`*/ \n\n `;
|
|
390
|
-
const fileContent = header + this.dboBuilder.tsTypesDefinition;
|
|
391
|
-
fs.readFile(fullPath, 'utf8', function(err, data) {
|
|
392
|
-
if (err || (force || data !== fileContent)) {
|
|
393
|
-
fs.writeFileSync(fullPath, fileContent);
|
|
394
|
-
console.log("Prostgles: Created typescript schema definition file: \n " + fileName)
|
|
395
|
-
}
|
|
396
|
-
});
|
|
397
|
-
} else if(force) {
|
|
398
|
-
console.error("Schema changed. tsGeneratedTypesDir needs to be set to reload server")
|
|
399
|
-
}
|
|
400
|
-
}
|
|
530
|
+
// if(this.watchSchema){
|
|
531
|
+
// if(!(await isSuperUser(db))) throw "Cannot watchSchema without a super user schema. Set watchSchema=false or provide a super user";
|
|
532
|
+
// }
|
|
533
|
+
|
|
534
|
+
this.dbEventsManager = new DBEventsManager(db, pgp);
|
|
401
535
|
|
|
402
|
-
refreshDBO = async () => {
|
|
403
|
-
if(this._dboBuilder) this._dboBuilder.destroy();
|
|
404
|
-
this.dboBuilder = await DboBuilder.create(this as any) as any;
|
|
405
|
-
if(!this.dboBuilder) throw "this.dboBuilder"
|
|
406
|
-
this.dbo = this.dboBuilder.dbo as any;
|
|
407
|
-
return this.dbo;
|
|
408
|
-
}
|
|
409
536
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
}
|
|
439
|
-
}, this.opts.watchSchema.checkIntervalMillis)
|
|
537
|
+
this.writeDBSchema();
|
|
538
|
+
|
|
539
|
+
/* 5. Finish init and provide DBO object */
|
|
540
|
+
try {
|
|
541
|
+
if (this.destroyed) {
|
|
542
|
+
console.trace(1)
|
|
543
|
+
}
|
|
544
|
+
onReady(this.dbo as any, this.db);
|
|
545
|
+
} catch (err) {
|
|
546
|
+
console.error("Prostgles: Error within onReady: \n", err)
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
this.loaded = true;
|
|
550
|
+
return {
|
|
551
|
+
db: this.dbo! as any,
|
|
552
|
+
_db: db,
|
|
553
|
+
pgp,
|
|
554
|
+
io: this.opts.io,
|
|
555
|
+
destroy: async () => {
|
|
556
|
+
console.log("destroying prgl instance")
|
|
557
|
+
this.destroyed = true;
|
|
558
|
+
if (this.opts.io) {
|
|
559
|
+
this.opts.io.on("connection", () => {
|
|
560
|
+
console.log("Socket connected to destroyed instance")
|
|
561
|
+
});
|
|
562
|
+
if (typeof this.opts.io.close === "function") {
|
|
563
|
+
this.opts.io.close();
|
|
564
|
+
console.log("this.io.close")
|
|
440
565
|
}
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
this.dboBuilder?.destroy();
|
|
569
|
+
this.dbo = undefined;
|
|
570
|
+
this.db = undefined;
|
|
571
|
+
await db.$pool.end();
|
|
572
|
+
await sleep(1000);
|
|
573
|
+
return true;
|
|
441
574
|
}
|
|
575
|
+
};
|
|
576
|
+
} catch (e) {
|
|
577
|
+
console.trace(e)
|
|
578
|
+
// @ts-ignore
|
|
579
|
+
throw "init issues: " + e.toString();
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
async runSQLFile(filePath: string) {
|
|
584
|
+
|
|
585
|
+
const fileContent = await this.getFileText(filePath);//.then(console.log);
|
|
586
|
+
|
|
587
|
+
return this.db?.multi(fileContent).then((data) => {
|
|
588
|
+
console.log("Prostgles: SQL file executed successfuly \n -> " + filePath);
|
|
589
|
+
return data
|
|
590
|
+
}).catch((err) => {
|
|
591
|
+
const { position, length } = err,
|
|
592
|
+
lines = fileContent.split("\n");
|
|
593
|
+
let errMsg = filePath + " error: ";
|
|
594
|
+
|
|
595
|
+
if (position && length && fileContent) {
|
|
596
|
+
const startLine = Math.max(0, fileContent.substring(0, position).split("\n").length - 2),
|
|
597
|
+
endLine = startLine + 3;
|
|
598
|
+
|
|
599
|
+
errMsg += "\n\n";
|
|
600
|
+
errMsg += lines.slice(startLine, endLine).map((txt, i) => `${startLine + i + 1} ${i === 1 ? "->" : " "} ${txt}`).join("\n");
|
|
601
|
+
errMsg += "\n\n";
|
|
602
|
+
}
|
|
603
|
+
console.error(errMsg, err);
|
|
604
|
+
throw err;
|
|
605
|
+
});
|
|
606
|
+
}
|
|
442
607
|
|
|
443
|
-
/* 1. Connect to db */
|
|
444
|
-
if(!this.db){
|
|
445
|
-
const { db, pgp } = getDbConnection(this.opts.dbConnection, this.opts.dbOptions, this.opts.DEBUG_MODE,
|
|
446
|
-
notice => {
|
|
447
|
-
if(this.opts.onNotice) this.opts.onNotice(notice);
|
|
448
|
-
if(this.dbEventsManager){
|
|
449
|
-
this.dbEventsManager.onNotice(notice)
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
);
|
|
453
|
-
this.db = db;
|
|
454
|
-
this.pgp = pgp;
|
|
455
|
-
this.isSuperUser = await isSuperUser(db);
|
|
456
|
-
}
|
|
457
|
-
this.checkDb();
|
|
458
|
-
const db = this.db!;
|
|
459
|
-
const pgp = this.pgp!;
|
|
460
608
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
}
|
|
609
|
+
connectedSockets: any[] = [];
|
|
610
|
+
async setSocketEvents() {
|
|
611
|
+
this.checkDb();
|
|
465
612
|
|
|
466
|
-
|
|
613
|
+
if (!this.dbo) throw "dbo missing";
|
|
467
614
|
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
this.tableConfigurator = new TableConfigurator(this as any);
|
|
471
|
-
try {
|
|
472
|
-
await this.tableConfigurator.init();
|
|
473
|
-
} catch(e){
|
|
474
|
-
console.error("TableConfigurator: ",e);
|
|
475
|
-
throw e;
|
|
476
|
-
}
|
|
477
|
-
}
|
|
615
|
+
let publishParser = new PublishParser(this.opts.publish, this.opts.publishMethods, this.opts.publishRawSQL, this.dbo, this.db!, this as any);
|
|
616
|
+
this.publishParser = publishParser;
|
|
478
617
|
|
|
479
|
-
|
|
480
|
-
await this.refreshDBO();
|
|
481
|
-
|
|
482
|
-
/* Create media table if required */
|
|
483
|
-
if(this.opts.fileTable){
|
|
484
|
-
const { awsS3Config, localConfig, imageOptions } = this.opts.fileTable;
|
|
485
|
-
await this.refreshDBO();
|
|
486
|
-
if(!awsS3Config && !localConfig) throw "fileTable missing param: Must provide awsS3Config OR localConfig";
|
|
487
|
-
//@ts-ignore
|
|
488
|
-
this.fileManager = new FileManager(awsS3Config || localConfig, imageOptions);
|
|
489
|
-
|
|
490
|
-
try {
|
|
491
|
-
await this.fileManager.init(this as any);
|
|
492
|
-
} catch(e){
|
|
493
|
-
console.error("FileManager: ",e);
|
|
494
|
-
throw e;
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
await this.refreshDBO();
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
if(this.opts.publish){
|
|
501
|
-
|
|
502
|
-
if(!this.opts.io) console.warn("IO missing. Publish has no effect without io");
|
|
503
|
-
|
|
504
|
-
/* 3.9 Check auth config */
|
|
505
|
-
this.authHandler = new AuthHandler(this as any);
|
|
506
|
-
await this.authHandler.init();
|
|
507
|
-
|
|
508
|
-
this.publishParser = new PublishParser(this.opts.publish, this.opts.publishMethods, this.opts.publishRawSQL, this.dbo!, this.db, this as any);
|
|
509
|
-
this.dboBuilder.publishParser = this.publishParser;
|
|
510
|
-
|
|
511
|
-
/* 4. Set publish and auth listeners */
|
|
512
|
-
await this.setSocketEvents();
|
|
513
|
-
|
|
514
|
-
} else if(this.opts.auth) throw "Auth config does not work without publish";
|
|
515
|
-
|
|
516
|
-
// if(this.watchSchema){
|
|
517
|
-
// if(!(await isSuperUser(db))) throw "Cannot watchSchema without a super user schema. Set watchSchema=false or provide a super user";
|
|
518
|
-
// }
|
|
519
|
-
|
|
520
|
-
this.dbEventsManager = new DBEventsManager(db, pgp);
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
this.writeDBSchema();
|
|
524
|
-
|
|
525
|
-
/* 5. Finish init and provide DBO object */
|
|
526
|
-
try {
|
|
527
|
-
if(this.destroyed) {
|
|
528
|
-
console.trace(1)
|
|
529
|
-
}
|
|
530
|
-
onReady(this.dbo as any, this.db);
|
|
531
|
-
} catch(err){
|
|
532
|
-
console.error("Prostgles: Error within onReady: \n", err)
|
|
533
|
-
}
|
|
618
|
+
if (!this.opts.io) return;
|
|
534
619
|
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
console.log("destroying prgl instance")
|
|
543
|
-
this.destroyed = true;
|
|
544
|
-
if(this.opts.io){
|
|
545
|
-
this.opts.io.on("connection", () => {
|
|
546
|
-
console.log("Socket connected to destroyed instance")
|
|
547
|
-
});
|
|
548
|
-
if(typeof this.opts.io.close === "function"){
|
|
549
|
-
this.opts.io.close();
|
|
550
|
-
console.log("this.io.close")
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
this.dboBuilder?.destroy();
|
|
555
|
-
this.dbo = undefined;
|
|
556
|
-
this.db = undefined;
|
|
557
|
-
await db.$pool.end();
|
|
558
|
-
await sleep(1000);
|
|
559
|
-
return true;
|
|
560
|
-
}
|
|
561
|
-
};
|
|
562
|
-
} catch (e) {
|
|
563
|
-
console.trace(e)
|
|
564
|
-
// @ts-ignore
|
|
565
|
-
throw "init issues: " + e.toString();
|
|
566
|
-
}
|
|
620
|
+
/* Already initialised. Only reconnect sockets */
|
|
621
|
+
if (this.connectedSockets.length) {
|
|
622
|
+
this.connectedSockets.forEach((s: any) => {
|
|
623
|
+
s.emit(CHANNELS.SCHEMA_CHANGED);
|
|
624
|
+
this.pushSocketSchema(s);
|
|
625
|
+
});
|
|
626
|
+
return;
|
|
567
627
|
}
|
|
568
628
|
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
if(position && length && fileContent){
|
|
582
|
-
const startLine = Math.max(0, fileContent.substring(0, position).split("\n").length - 2),
|
|
583
|
-
endLine = startLine + 3;
|
|
584
|
-
|
|
585
|
-
errMsg += "\n\n";
|
|
586
|
-
errMsg += lines.slice(startLine, endLine).map((txt, i) => `${startLine + i + 1} ${i === 1? "->" : " "} ${txt}`).join("\n");
|
|
587
|
-
errMsg += "\n\n";
|
|
588
|
-
}
|
|
589
|
-
console.error(errMsg, err);
|
|
590
|
-
throw err;
|
|
591
|
-
});
|
|
592
|
-
}
|
|
629
|
+
/* Initialise */
|
|
630
|
+
this.opts.io.on('connection', async (socket: PRGLIOSocket) => {
|
|
631
|
+
if (this.destroyed) {
|
|
632
|
+
console.log("Socket connected to destroyed instance");
|
|
633
|
+
socket.disconnect();
|
|
634
|
+
return
|
|
635
|
+
}
|
|
636
|
+
this.connectedSockets.push(socket);
|
|
637
|
+
|
|
638
|
+
if (!this.db || !this.dbo) throw "db/dbo missing";
|
|
639
|
+
let { dbo, db, pgp } = this;
|
|
593
640
|
|
|
641
|
+
try {
|
|
642
|
+
if (this.opts.onSocketConnect) await this.opts.onSocketConnect(socket, dbo as any, db);
|
|
594
643
|
|
|
595
|
-
connectedSockets: any[] = [];
|
|
596
|
-
async setSocketEvents(){
|
|
597
|
-
this.checkDb();
|
|
598
644
|
|
|
599
|
-
|
|
645
|
+
/* RUN Client request from Publish.
|
|
646
|
+
Checks request against publish and if OK run it with relevant publish functions. Local (server) requests do not check the policy
|
|
647
|
+
*/
|
|
648
|
+
socket.removeAllListeners(CHANNELS.DEFAULT)
|
|
649
|
+
socket.on(CHANNELS.DEFAULT, async ({ tableName, command, param1, param2, param3 }: SocketRequestParams, cb = (...callback: any[]) => { }) => {
|
|
600
650
|
|
|
601
|
-
|
|
602
|
-
|
|
651
|
+
try { /* Channel name will only include client-sent params so we ignore table_rules enforced params */
|
|
652
|
+
if (!socket || !this.authHandler || !this.publishParser || !this.dbo) {
|
|
653
|
+
console.error("socket or authhandler missing??!!")
|
|
654
|
+
throw "socket or authhandler missing??!!";
|
|
655
|
+
}
|
|
603
656
|
|
|
604
|
-
|
|
657
|
+
const clientInfo = await this.authHandler.getClientInfo({ socket });
|
|
658
|
+
let valid_table_command_rules = await this.publishParser.getValidatedRequestRule({ tableName, command, localParams: { socket } }, clientInfo);
|
|
659
|
+
if (valid_table_command_rules) {
|
|
660
|
+
//@ts-ignore
|
|
661
|
+
let res = await this.dbo[tableName][command]!(param1, param2, param3, valid_table_command_rules, { socket, has_rules: true });
|
|
662
|
+
cb(null, res);
|
|
663
|
+
} else throw `Invalid OR disallowed request: ${tableName}.${command} `;
|
|
664
|
+
|
|
665
|
+
} catch (err) {
|
|
666
|
+
// const _err_msg = err.toString();
|
|
667
|
+
// cb({ msg: _err_msg, err });
|
|
668
|
+
console.trace(err);
|
|
669
|
+
cb(err)
|
|
670
|
+
// console.warn("runPublishedRequest ERROR: ", err, socket._user);
|
|
671
|
+
}
|
|
672
|
+
});
|
|
605
673
|
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
674
|
+
socket.on("disconnect", () => {
|
|
675
|
+
this.dbEventsManager?.removeNotice(socket);
|
|
676
|
+
this.dbEventsManager?.removeNotify(undefined, socket);
|
|
677
|
+
this.connectedSockets = this.connectedSockets.filter(s => s.id !== socket.id);
|
|
678
|
+
// subscriptions = subscriptions.filter(sub => sub.socket.id !== socket.id);
|
|
679
|
+
if (this.opts.onSocketDisconnect) {
|
|
680
|
+
this.opts.onSocketDisconnect(socket, dbo as any);
|
|
681
|
+
};
|
|
682
|
+
});
|
|
683
|
+
|
|
684
|
+
socket.removeAllListeners(CHANNELS.METHOD)
|
|
685
|
+
socket.on(CHANNELS.METHOD, async ({ method, params }: SocketMethodRequest, cb = (...callback: any) => { }) => {
|
|
686
|
+
try {
|
|
687
|
+
const methods = await this.publishParser?.getMethods(socket) as any;
|
|
688
|
+
|
|
689
|
+
if (!methods || !methods[method]) {
|
|
690
|
+
cb("Disallowed/missing method " + JSON.stringify(method));
|
|
691
|
+
} else {
|
|
692
|
+
try {
|
|
693
|
+
const res = await methods[method](...params);
|
|
694
|
+
cb(null, res);
|
|
695
|
+
} catch (err) {
|
|
696
|
+
makeSocketError(cb, err);
|
|
697
|
+
}
|
|
621
698
|
}
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
} catch(err){
|
|
682
|
-
makeSocketError(cb, err);
|
|
683
|
-
}
|
|
684
|
-
}
|
|
685
|
-
} catch(err) {
|
|
686
|
-
makeSocketError(cb, err);
|
|
687
|
-
console.warn("method ERROR: ", err, socket._user);
|
|
688
|
-
}
|
|
689
|
-
});
|
|
690
|
-
|
|
691
|
-
this.pushSocketSchema(socket);
|
|
692
|
-
} catch(e) {
|
|
693
|
-
console.trace("setSocketEvents: ", e)
|
|
694
|
-
}
|
|
699
|
+
} catch (err) {
|
|
700
|
+
makeSocketError(cb, err);
|
|
701
|
+
console.warn("method ERROR: ", err, socket._user);
|
|
702
|
+
}
|
|
703
|
+
});
|
|
704
|
+
|
|
705
|
+
this.pushSocketSchema(socket);
|
|
706
|
+
} catch (e) {
|
|
707
|
+
console.trace("setSocketEvents: ", e)
|
|
708
|
+
}
|
|
709
|
+
});
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
pushSocketSchema = async (socket: any) => {
|
|
713
|
+
|
|
714
|
+
let auth: any = await this.authHandler?.makeSocketAuth(socket) || {};
|
|
715
|
+
|
|
716
|
+
// let needType = this.publishRawSQL && typeof this.publishRawSQL === "function";
|
|
717
|
+
// let DATA_TYPES = !needType? [] : await this.db.any("SELECT oid, typname FROM pg_type");
|
|
718
|
+
// let USER_TABLES = !needType? [] : await this.db.any("SELECT relid, relname FROM pg_catalog.pg_statio_user_tables");
|
|
719
|
+
|
|
720
|
+
const { dbo, db, pgp, publishParser } = this;
|
|
721
|
+
let fullSchema: {
|
|
722
|
+
schema: TableSchemaForClient;
|
|
723
|
+
tables: DBSchemaTable[];
|
|
724
|
+
} | undefined;
|
|
725
|
+
let publishValidationError;
|
|
726
|
+
let rawSQL = false;
|
|
727
|
+
|
|
728
|
+
try {
|
|
729
|
+
if (!publishParser) throw "publishParser undefined";
|
|
730
|
+
fullSchema = await publishParser.getSchemaFromPublish(socket);
|
|
731
|
+
} catch (e) {
|
|
732
|
+
publishValidationError = "Server Error: PUBLISH VALIDATION ERROR";
|
|
733
|
+
console.error(`\nProstgles PUBLISH VALIDATION ERROR (after socket connected):\n ->`, e);
|
|
734
|
+
}
|
|
735
|
+
socket.prostgles = socket.prostgles || {};
|
|
736
|
+
socket.prostgles.schema = fullSchema?.schema;
|
|
737
|
+
/* RUN Raw sql from client IF PUBLISHED
|
|
738
|
+
*/
|
|
739
|
+
|
|
740
|
+
if (this.opts.publishRawSQL && typeof this.opts.publishRawSQL === "function") {
|
|
741
|
+
const canRunSQL = async () => {
|
|
742
|
+
const publishParams = await this.publishParser?.getPublishParams({ socket })
|
|
743
|
+
let res = await this.opts.publishRawSQL?.(publishParams as any);
|
|
744
|
+
return Boolean(res && typeof res === "boolean" || res === "*");
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
if (await canRunSQL()) {
|
|
748
|
+
socket.removeAllListeners(CHANNELS.SQL)
|
|
749
|
+
socket.on(CHANNELS.SQL, async ({ query, params, options }: SQLRequest, cb = (...callback: any) => { }) => {
|
|
750
|
+
|
|
751
|
+
if (!this.dbo?.sql) throw "Internal error: sql handler missing";
|
|
752
|
+
|
|
753
|
+
this.dbo.sql(query, params, options, { socket }).then(res => {
|
|
754
|
+
cb(null, res)
|
|
755
|
+
}).catch(err => {
|
|
756
|
+
makeSocketError(cb, err);
|
|
757
|
+
})
|
|
695
758
|
});
|
|
759
|
+
if (db) {
|
|
760
|
+
// let allTablesViews = await db.any(STEP2_GET_ALL_TABLES_AND_COLUMNS);
|
|
761
|
+
// fullSchema = allTablesViews;
|
|
762
|
+
rawSQL = true;
|
|
763
|
+
} else console.error("db missing");
|
|
764
|
+
}
|
|
696
765
|
}
|
|
697
766
|
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
tables: DBSchemaTable[];
|
|
710
|
-
} | undefined;
|
|
711
|
-
let publishValidationError;
|
|
712
|
-
let rawSQL = false;
|
|
713
|
-
|
|
714
|
-
try {
|
|
715
|
-
if(!publishParser) throw "publishParser undefined";
|
|
716
|
-
fullSchema = await publishParser.getSchemaFromPublish(socket);
|
|
717
|
-
} catch(e){
|
|
718
|
-
publishValidationError = "Server Error: PUBLISH VALIDATION ERROR";
|
|
719
|
-
console.error(`\nProstgles PUBLISH VALIDATION ERROR (after socket connected):\n ->`, e);
|
|
720
|
-
}
|
|
721
|
-
socket.prostgles = socket.prostgles || {};
|
|
722
|
-
socket.prostgles.schema = fullSchema?.schema;
|
|
723
|
-
/* RUN Raw sql from client IF PUBLISHED
|
|
724
|
-
*/
|
|
725
|
-
|
|
726
|
-
if(this.opts.publishRawSQL && typeof this.opts.publishRawSQL === "function"){
|
|
727
|
-
const canRunSQL = async () => {
|
|
728
|
-
const publishParams = await this.publishParser?.getPublishParams({ socket })
|
|
729
|
-
let res = await this.opts.publishRawSQL?.(publishParams as any);
|
|
730
|
-
return Boolean(res && typeof res === "boolean" || res === "*");
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
if(await canRunSQL()){
|
|
734
|
-
socket.removeAllListeners(CHANNELS.SQL)
|
|
735
|
-
socket.on(CHANNELS.SQL, async ({ query, params, options }: SQLRequest, cb = (...callback: any) => {}) => {
|
|
736
|
-
|
|
737
|
-
if(!this.dbo?.sql) throw "Internal error: sql handler missing";
|
|
738
|
-
|
|
739
|
-
this.dbo.sql(query, params, options, { socket }).then(res => {
|
|
740
|
-
cb(null, res)
|
|
741
|
-
}).catch(err => {
|
|
742
|
-
makeSocketError(cb, err);
|
|
743
|
-
})
|
|
744
|
-
});
|
|
745
|
-
if(db){
|
|
746
|
-
// let allTablesViews = await db.any(STEP2_GET_ALL_TABLES_AND_COLUMNS);
|
|
747
|
-
// fullSchema = allTablesViews;
|
|
748
|
-
rawSQL = true;
|
|
749
|
-
} else console.error("db missing");
|
|
750
|
-
}
|
|
767
|
+
const { schema, tables } = fullSchema ?? { schema: {}, tables: [] };
|
|
768
|
+
let joinTables2: string[][] = [];
|
|
769
|
+
if (this.opts.joins) {
|
|
770
|
+
// joinTables = Array.from(new Set(flat(this.dboBuilder.getJoins().map(j => j.tables)).filter(t => schema[t])));
|
|
771
|
+
let _joinTables2 = this.dboBuilder.getJoinPaths()
|
|
772
|
+
.filter(jp =>
|
|
773
|
+
![jp.t1, jp.t2].find(t => !schema[t] || !schema[t].findOne)
|
|
774
|
+
).map(jp => [jp.t1, jp.t2].sort());
|
|
775
|
+
_joinTables2.map(jt => {
|
|
776
|
+
if (!joinTables2.find(_jt => _jt.join() === jt.join())) {
|
|
777
|
+
joinTables2.push(jt);
|
|
751
778
|
}
|
|
779
|
+
});
|
|
780
|
+
}
|
|
752
781
|
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
joinTables2.push(jt);
|
|
764
|
-
}
|
|
765
|
-
});
|
|
766
|
-
}
|
|
767
|
-
|
|
768
|
-
const methods = await publishParser?.getMethods(socket);
|
|
769
|
-
const clientSchema: ClientSchema = {
|
|
770
|
-
schema,
|
|
771
|
-
methods: getKeys(methods),
|
|
772
|
-
tableSchema: tables,
|
|
773
|
-
rawSQL,
|
|
774
|
-
joinTables: joinTables2,
|
|
775
|
-
auth,
|
|
776
|
-
version,
|
|
777
|
-
err: publishValidationError
|
|
778
|
-
}
|
|
779
|
-
socket.emit(CHANNELS.SCHEMA, clientSchema);
|
|
782
|
+
const methods = await publishParser?.getMethods(socket);
|
|
783
|
+
const clientSchema: ClientSchema = {
|
|
784
|
+
schema,
|
|
785
|
+
methods: getKeys(methods),
|
|
786
|
+
tableSchema: tables,
|
|
787
|
+
rawSQL,
|
|
788
|
+
joinTables: joinTables2,
|
|
789
|
+
auth,
|
|
790
|
+
version,
|
|
791
|
+
err: publishValidationError
|
|
780
792
|
}
|
|
793
|
+
socket.emit(CHANNELS.SCHEMA, clientSchema);
|
|
794
|
+
}
|
|
781
795
|
}
|
|
782
|
-
function makeSocketError(cb: Function, err: any){
|
|
783
|
-
|
|
784
|
-
err.toString() :
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
796
|
+
function makeSocketError(cb: Function, err: any) {
|
|
797
|
+
const err_msg = (err instanceof Error) ?
|
|
798
|
+
err.toString() :
|
|
799
|
+
isPlainObject(err) ?
|
|
800
|
+
JSON.stringify(err, null, 2) :
|
|
801
|
+
(err as any).toString(),
|
|
802
|
+
e = { err_msg, err };
|
|
803
|
+
cb(e);
|
|
790
804
|
}
|
|
791
805
|
|
|
792
806
|
type SocketRequestParams = {
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
807
|
+
tableName: string;
|
|
808
|
+
command: typeof TABLE_METHODS[number];
|
|
809
|
+
param1: any;
|
|
810
|
+
param2: any;
|
|
811
|
+
param3: any;
|
|
798
812
|
}
|
|
799
813
|
type SocketMethodRequest = {
|
|
800
|
-
|
|
801
|
-
|
|
814
|
+
method: string;
|
|
815
|
+
params: any;
|
|
802
816
|
}
|
|
803
817
|
|
|
804
818
|
// const ALL_PUBLISH_METHODS = ["update", "upsert", "delete", "insert", "find", "findOne", "subscribe", "unsubscribe", "sync", "unsync", "remove"];
|
|
@@ -809,14 +823,14 @@ type SocketMethodRequest = {
|
|
|
809
823
|
// let res = arr.reduce(function (farr, toFlatten) {
|
|
810
824
|
// return farr.concat(Array.isArray(toFlatten) ? flat(toFlatten) : toFlatten);
|
|
811
825
|
// }, []);
|
|
812
|
-
|
|
826
|
+
|
|
813
827
|
// return res;
|
|
814
828
|
// }
|
|
815
829
|
|
|
816
830
|
|
|
817
831
|
|
|
818
|
-
export async function isSuperUser(db: DB): Promise<boolean>{
|
|
819
|
-
|
|
832
|
+
export async function isSuperUser(db: DB): Promise<boolean> {
|
|
833
|
+
return db.oneOrNone("select usesuper from pg_user where usename = CURRENT_USER;").then(r => r.usesuper);
|
|
820
834
|
}
|
|
821
835
|
|
|
822
836
|
|