prostgles-server 2.0.173 → 2.0.176

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/.vscode/settings.json +3 -0
  2. package/dist/AuthHandler.d.ts +13 -12
  3. package/dist/AuthHandler.d.ts.map +1 -1
  4. package/dist/AuthHandler.js +5 -2
  5. package/dist/AuthHandler.js.map +1 -1
  6. package/dist/DBSchemaBuilder.d.ts +11 -0
  7. package/dist/DBSchemaBuilder.d.ts.map +1 -0
  8. package/dist/DBSchemaBuilder.js +56 -0
  9. package/dist/DBSchemaBuilder.js.map +1 -0
  10. package/dist/DboBuilder.d.ts +23 -22
  11. package/dist/DboBuilder.d.ts.map +1 -1
  12. package/dist/DboBuilder.js +36 -61
  13. package/dist/DboBuilder.js.map +1 -1
  14. package/dist/FileManager.d.ts +2 -2
  15. package/dist/FileManager.d.ts.map +1 -1
  16. package/dist/Filtering.d.ts.map +1 -1
  17. package/dist/Filtering.js.map +1 -1
  18. package/dist/Prostgles.d.ts +25 -257
  19. package/dist/Prostgles.d.ts.map +1 -1
  20. package/dist/Prostgles.js +12 -376
  21. package/dist/Prostgles.js.map +1 -1
  22. package/dist/PubSubManager.d.ts +6 -5
  23. package/dist/PubSubManager.d.ts.map +1 -1
  24. package/dist/PubSubManager.js.map +1 -1
  25. package/dist/PublishParser.d.ts +262 -0
  26. package/dist/PublishParser.d.ts.map +1 -0
  27. package/dist/PublishParser.js +391 -0
  28. package/dist/PublishParser.js.map +1 -0
  29. package/dist/QueryBuilder.d.ts +20 -4
  30. package/dist/QueryBuilder.d.ts.map +1 -1
  31. package/dist/QueryBuilder.js.map +1 -1
  32. package/dist/TableConfig.d.ts +6 -3
  33. package/dist/TableConfig.d.ts.map +1 -1
  34. package/dist/TableConfig.js +28 -1
  35. package/dist/TableConfig.js.map +1 -1
  36. package/dist/index.d.ts +3 -3
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +4 -0
  39. package/dist/index.js.map +1 -1
  40. package/lib/AuthHandler.ts +25 -19
  41. package/lib/DBSchemaBuilder.ts +91 -0
  42. package/lib/DboBuilder.ts +84 -98
  43. package/lib/FileManager.ts +2 -2
  44. package/lib/Filtering.ts +3 -3
  45. package/lib/Prostgles.ts +33 -704
  46. package/lib/PubSubManager.ts +6 -5
  47. package/lib/PublishParser.ts +723 -0
  48. package/lib/QueryBuilder.ts +6 -5
  49. package/lib/TableConfig.ts +36 -5
  50. package/lib/index.ts +4 -4
  51. package/package.json +2 -2
  52. package/tests/client/PID.txt +1 -1
  53. package/tests/client/index.js +2 -2
  54. package/tests/client/index.ts +2 -2
  55. package/tests/client/package-lock.json +15 -15
  56. package/tests/client/package.json +1 -1
  57. package/tests/client_only_queries.js +24 -1
  58. package/tests/client_only_queries.ts +23 -1
  59. package/tests/isomorphic_queries.js +3 -0
  60. package/tests/isomorphic_queries.ts +5 -2
  61. package/tests/server/DBoGenerated.d.ts +428 -286
  62. package/tests/server/index.js +1 -14
  63. package/tests/server/index.ts +5 -19
  64. package/tests/server/package-lock.json +3 -3
@@ -0,0 +1,723 @@
1
+ import { getKeys, RULE_METHODS, AnyObject, get, TableSchemaForClient, DBSchemaTable, MethodKey, TableInfo, FullFilter, DBSchemaColumns, DBSchema, DBTableSchema } from "prostgles-types";
2
+ import { ClientInfo } from "./AuthHandler";
3
+ import { CommonTableRules, Filter, isPlainObject, LocalParams, PRGLIOSocket, TableHandler, ViewHandler } from "./DboBuilder";
4
+ import { Prostgles, DBHandlerServer, DB, TABLE_METHODS } from "./Prostgles";
5
+ import type { DBOFullyTyped, PublishFullyTyped } from "./DBSchemaBuilder";
6
+ export type Method = (...args: any) => ( any | Promise<any> );
7
+ export type PublishMethods<S extends DBSchema> = (params: PublishParams<S>) => { [key:string]: Method } | Promise<{ [key:string]: Method }>;
8
+
9
+ export type Awaitable<T> = T | Promise<T>;
10
+
11
+ type Request = {
12
+ socket?: any;
13
+ httpReq?: any;
14
+ }
15
+
16
+ type DboTable = Request & {
17
+ tableName: string;
18
+ localParams: LocalParams;
19
+ }
20
+ type DboTableCommand = Request & DboTable & {
21
+ command: string;
22
+ localParams: LocalParams;
23
+ }
24
+
25
+ const RULE_TO_METHODS = [
26
+ {
27
+ rule: "getColumns",
28
+ sqlRule: "select",
29
+ methods: RULE_METHODS.getColumns,
30
+ no_limits: true,
31
+ allowed_params: [],
32
+ table_only: false,
33
+ hint: ` expecting false | true | undefined`
34
+ },
35
+ {
36
+ rule: "getInfo",
37
+ sqlRule: "select",
38
+ methods: RULE_METHODS.getInfo,
39
+ no_limits: true,
40
+ allowed_params: [],
41
+ table_only: false,
42
+ hint: ` expecting false | true | undefined`
43
+ },
44
+ {
45
+ rule: "insert",
46
+ sqlRule: "insert",
47
+ methods: RULE_METHODS.insert,
48
+ no_limits: <SelectRule>{ fields: "*" },
49
+ table_only: true,
50
+ allowed_params: <Array<keyof InsertRule>>["fields", "forcedData", "returningFields", "validate", "preValidate"] ,
51
+ hint: ` expecting "*" | true | { fields: string | string[] | {} }`
52
+ },
53
+ {
54
+ rule: "update",
55
+ sqlRule: "update",
56
+ methods: RULE_METHODS.update,
57
+ no_limits: <UpdateRule>{ fields: "*", filterFields: "*", returningFields: "*" },
58
+ table_only: true,
59
+ allowed_params: <Array<keyof UpdateRule>>["fields", "filterFields", "forcedFilter", "forcedData", "returningFields", "validate", "dynamicFields"] ,
60
+ hint: ` expecting "*" | true | { fields: string | string[] | {} }`
61
+ },
62
+ {
63
+ rule: "select",
64
+ sqlRule: "select",
65
+ methods: RULE_METHODS.select,
66
+ no_limits: <SelectRule>{ fields: "*", filterFields: "*" },
67
+ table_only: false,
68
+ allowed_params: <Array<keyof SelectRule>>["fields", "filterFields", "forcedFilter", "validate", "maxLimit"] ,
69
+ hint: ` expecting "*" | true | { fields: ( string | string[] | {} ) }`
70
+ },
71
+ {
72
+ rule: "delete",
73
+ sqlRule: "delete",
74
+ methods: RULE_METHODS.delete,
75
+ no_limits: <DeleteRule>{ filterFields: "*" } ,
76
+ table_only: true,
77
+ allowed_params: <Array<keyof DeleteRule>>["filterFields", "forcedFilter", "returningFields", "validate"],
78
+ hint: ` expecting "*" | true | { filterFields: ( string | string[] | {} ) } \n Will use "select", "update", "delete" and "insert" rules`
79
+ },
80
+ {
81
+ rule: "sync",
82
+ sqlRule: "select",
83
+ methods: RULE_METHODS.sync,
84
+ no_limits: null,
85
+ table_only: true,
86
+ allowed_params: <Array<keyof SyncRule>>["id_fields", "synced_field", "sync_type", "allow_delete", "throttle", "batch_size"],
87
+ hint: ` expecting "*" | true | { id_fields: string[], synced_field: string }`
88
+ },
89
+ {
90
+ rule: "subscribe",
91
+ sqlRule: "select",
92
+ methods: RULE_METHODS.subscribe,
93
+ no_limits: <SubscribeRule>{ throttle: 0 },
94
+ table_only: true,
95
+ allowed_params: <Array<keyof SubscribeRule>>["throttle"],
96
+ hint: ` expecting "*" | true | { throttle: number } \n Will use "select" rules`
97
+ }
98
+ ] as const;
99
+
100
+ import { FieldFilter, SelectParams } from "prostgles-types";
101
+ import { DEFAULT_SYNC_BATCH_SIZE } from "./PubSubManager";
102
+
103
+ export type InsertRequestData = {
104
+ data: object | object[]
105
+ returning: FieldFilter;
106
+ }
107
+ export type SelectRequestData = {
108
+ filter: object;
109
+ params: SelectParams;
110
+ }
111
+ export type DeleteRequestData = {
112
+ filter: object;
113
+ returning: FieldFilter;
114
+ }
115
+ export type UpdateRequestDataOne<R> = {
116
+ filter: FullFilter<R>
117
+ data: Partial<R>;
118
+ returning: FieldFilter<R>;
119
+ }
120
+ export type UpdateReq<R> = {
121
+ filter: FullFilter<R>
122
+ data: Partial<R>;
123
+ }
124
+ export type UpdateRequestDataBatch<R> = {
125
+ data: UpdateReq<R>[];
126
+ }
127
+ export type UpdateRequestData<R extends AnyObject = AnyObject> = UpdateRequestDataOne<R> | UpdateRequestDataBatch<R>;
128
+
129
+ export type ValidateRow<R extends AnyObject = AnyObject> = (row: R) => R | Promise<R>;
130
+ export type ValidateUpdateRow<R extends AnyObject = AnyObject> = (args: { update: Partial<R>, filter: FullFilter<R> }) => R | Promise<R>;
131
+
132
+ export type SelectRule<S extends DBTableSchema = any> = {
133
+
134
+ /**
135
+ * Fields allowed to be selected. Tip: Use false to exclude field
136
+ */
137
+ fields: FieldFilter<DBSchemaColumns<S["columns"]>>;
138
+
139
+ /**
140
+ * The maximum number of rows a user can get in a select query. null by default. Unless a null or higher limit is specified 100 rows will be returned by the default
141
+ */
142
+ maxLimit?: number | null;
143
+
144
+ /**
145
+ * Filter added to every query (e.g. user_id) to restrict access
146
+ */
147
+ forcedFilter?: FullFilter<DBSchemaColumns<S["columns"]>>;
148
+
149
+ /**
150
+ * Fields user can filter by
151
+ * */
152
+ filterFields?: FieldFilter<DBSchemaColumns<S["columns"]>>;
153
+
154
+ /**
155
+ * Validation logic to check/update data for each request
156
+ */
157
+ validate?(args: SelectRequestData): SelectRequestData | Promise<SelectRequestData>;
158
+
159
+ }
160
+ export type InsertRule<S extends DBTableSchema = any> = {
161
+
162
+ /**
163
+ * Fields allowed to be inserted. Tip: Use false to exclude field
164
+ */
165
+ fields: FieldFilter<DBSchemaColumns<S["columns"]>>;
166
+
167
+ /**
168
+ * Data to include/overwrite on each insert
169
+ */
170
+ forcedData?: Partial<DBSchemaColumns<S["columns"]>>;
171
+
172
+ /**
173
+ * Fields user can view after inserting
174
+ */
175
+ returningFields?: FieldFilter<DBSchemaColumns<S["columns"]>>;
176
+
177
+ /**
178
+ * Validation logic to check/update data for each request. Happens before publish rule checks (for fields, forcedData/forcedFilter)
179
+ */
180
+ preValidate?: ValidateRow<DBSchemaColumns<S["columns"]>>;
181
+
182
+ /**
183
+ * Validation logic to check/update data for each request. Happens after publish rule checks (for fields, forcedData/forcedFilter)
184
+ */
185
+ validate?: ValidateRow<DBSchemaColumns<S["columns"]>>;
186
+ }
187
+ export type UpdateRule<S extends DBTableSchema = any> = {
188
+
189
+ /**
190
+ * Fields allowed to be updated. Tip: Use false/0 to exclude field
191
+ */
192
+ fields: FieldFilter<DBSchemaColumns<S["columns"]>>;
193
+
194
+ /**
195
+ * Row level FGAC
196
+ * Used when the editable fields change based on the updated row
197
+ * If specified then the fields from the first matching filter table.count({ ...filter, ...updateFilter }) > 0 will be used
198
+ * If none matching then the "fields" will be used
199
+ * Specify in decreasing order of specificity otherwise a more general filter will match first
200
+ */
201
+ dynamicFields?: {
202
+ filter: FullFilter<DBSchemaColumns<S["columns"]>>;
203
+ fields: FieldFilter<DBSchemaColumns<S["columns"]>>;
204
+ }[];
205
+
206
+ /**
207
+ * Filter added to every query (e.g. user_id) to restrict access
208
+ * This filter cannot be updated
209
+ */
210
+ forcedFilter?: FullFilter<DBSchemaColumns<S["columns"]>>;
211
+
212
+ /**
213
+ * Data to include/overwrite on each updatDBe
214
+ */
215
+ forcedData?: Partial<DBSchemaColumns<S["columns"]>>
216
+
217
+ /**
218
+ * Fields user can use to find the updates
219
+ */
220
+ filterFields?: FieldFilter<DBSchemaColumns<S["columns"]>>;
221
+
222
+ /**
223
+ * Fields user can view after updating
224
+ */
225
+ returningFields?: FieldFilter<DBSchemaColumns<S["columns"]>>;
226
+
227
+ /**
228
+ * Validation logic to check/update data for each request
229
+ */
230
+ validate?: ValidateUpdateRow<DBSchemaColumns<S["columns"]>>;
231
+
232
+ };
233
+
234
+ export type DeleteRule<S extends DBTableSchema = any> = {
235
+
236
+ /**
237
+ * Filter added to every query (e.g. user_id) to restrict access
238
+ */
239
+ forcedFilter?: FullFilter<DBSchemaColumns<S["columns"]>>;
240
+
241
+ /**
242
+ * Fields user can filter by
243
+ */
244
+ filterFields?: FieldFilter<DBSchemaColumns<S["columns"]>>;
245
+
246
+ /**
247
+ * Fields user can view after deleting
248
+ */
249
+ returningFields?: FieldFilter<DBSchemaColumns<S["columns"]>>;
250
+
251
+ /**
252
+ * Validation logic to check/update data for each request
253
+ */
254
+ validate?(...args: any[]): UpdateRequestData<DBSchemaColumns<S["columns"]>>;
255
+ }
256
+ export type SyncRule<S extends DBTableSchema = any> = {
257
+
258
+ /**
259
+ * Primary keys used in updating data
260
+ */
261
+ id_fields: string[];
262
+
263
+ /**
264
+ * Numerical incrementing fieldname (last updated timestamp) used to sync items
265
+ */
266
+ synced_field: string;
267
+
268
+ /**
269
+ * EXPERIMENTAL. Disabled by default. If true then server will attempt to delete any records missing from client.
270
+ */
271
+ allow_delete?: boolean;
272
+
273
+ /**
274
+ * Throttle replication transmission in milliseconds. Defaults to 100
275
+ */
276
+ throttle?: number;
277
+
278
+ /**
279
+ * Number of rows to send per trip. Defaults to 50
280
+ */
281
+ batch_size?: number;
282
+ }
283
+ export type SubscribeRule = {
284
+ throttle?: number;
285
+ }
286
+
287
+ export type ViewRule<S extends DBTableSchema> = CommonTableRules & {
288
+ /**
289
+ * What can be read from the table
290
+ */
291
+ select?: SelectRule<S>;
292
+ };
293
+ export type TableRule<S extends DBTableSchema = any> = ViewRule<S> & {
294
+ insert?: InsertRule<S>;
295
+ update?: UpdateRule<S>;
296
+ delete?: DeleteRule<S>;
297
+ sync?: SyncRule<S>;
298
+ subscribe?: SubscribeRule;
299
+ };
300
+ export type PublishViewRule<S extends DBTableSchema = any> = {
301
+ select?: SelectRule<S> | PublishAllOrNothing
302
+ getColumns?: PublishAllOrNothing;
303
+ getInfo?: PublishAllOrNothing;
304
+ };
305
+ export type PublishTableRule<S extends DBTableSchema = any> = PublishViewRule<S> & {
306
+ insert?: InsertRule<S> | PublishAllOrNothing
307
+ update?: UpdateRule<S> | PublishAllOrNothing
308
+ delete?: DeleteRule<S> | PublishAllOrNothing
309
+ sync?: SyncRule<S>;
310
+ subscribe?: SubscribeRule | PublishAllOrNothing;
311
+ };
312
+
313
+
314
+ export type ParsedPublishTable = {
315
+ select?: SelectRule
316
+ getColumns?: true;
317
+ getInfo?: true;
318
+
319
+ insert?: InsertRule;
320
+ update?: UpdateRule;
321
+ delete?: DeleteRule;
322
+ sync?: SyncRule;
323
+ subscribe?: SubscribeRule;
324
+ subscribeOne?: SubscribeRule;
325
+ }
326
+
327
+ // export type Publish = {
328
+ // tablesOrViews: {[key:string]: TableRule | ViewRule | "*" }
329
+ // }
330
+ export type PublishParams<S extends DBSchema = any> = {
331
+ sid?: string;
332
+ dbo: DBOFullyTyped<S>;
333
+ db?: DB;
334
+ user?: AnyObject;
335
+ socket: PRGLIOSocket
336
+ }
337
+ export type RequestParams = { dbo?: DBHandlerServer, socket?: any };
338
+ export type PublishAllOrNothing = true | "*" | false | null;
339
+ export type PublishObject<Schema extends DBSchema = any> = {
340
+ [table_name: string]: (PublishTableRule | PublishViewRule | PublishAllOrNothing )
341
+ };
342
+ export type ParsedPublishTables = {
343
+ [table_name: string]: ParsedPublishTable
344
+ };
345
+ export type PublishedResult<Schema extends DBSchema = any> = PublishAllOrNothing | PublishFullyTyped<Schema> ;
346
+ export type Publish<Schema extends DBSchema = any> = PublishedResult<Schema> | ((params: PublishParams<Schema>) => Awaitable<PublishedResult<Schema>>);
347
+
348
+ export class PublishParser {
349
+ publish: any;
350
+ publishMethods?: any;
351
+ publishRawSQL?: any;
352
+ dbo: DBHandlerServer;
353
+ db: DB
354
+ prostgles: Prostgles;
355
+
356
+ constructor(publish: any, publishMethods: any, publishRawSQL: any, dbo: DBHandlerServer, db: DB, prostgles: Prostgles){
357
+ this.publish = publish;
358
+ this.publishMethods = publishMethods;
359
+ this.publishRawSQL = publishRawSQL;
360
+ this.dbo = dbo;
361
+ this.db = db;
362
+ this.prostgles = prostgles;
363
+
364
+ if(!this.dbo || !this.publish) throw "INTERNAL ERROR: dbo and/or publish missing";
365
+ }
366
+
367
+ async getPublishParams(localParams: LocalParams, clientInfo?: ClientInfo): Promise<PublishParams<any>> {
368
+ if(!this.dbo) throw "dbo missing"
369
+ return {
370
+ ...(clientInfo || await this.prostgles.authHandler?.getClientInfo(localParams)),
371
+ dbo: this.dbo,
372
+ db: this.db,
373
+ socket: localParams.socket!
374
+ }
375
+ }
376
+
377
+ async getMethods(socket: any){
378
+ let methods = {};
379
+
380
+ const publishParams = await this.getPublishParams({ socket });
381
+ const _methods = await applyParamsIfFunc(this.publishMethods, publishParams);
382
+
383
+ if(_methods && Object.keys(_methods).length){
384
+ getKeys(_methods).map(key => {
385
+ if(_methods[key] && (typeof _methods[key] === "function" || typeof _methods[key].then === "function")){
386
+ //@ts-ignore
387
+ methods[key] = _methods[key];
388
+ } else {
389
+ throw `invalid publishMethods item -> ${key} \n Expecting a function or promise`
390
+ }
391
+ });
392
+ }
393
+
394
+ return methods;
395
+ }
396
+
397
+ /**
398
+ * Parses the first level of publish. (If false then nothing if * then all tables and views)
399
+ * @param socket
400
+ * @param user
401
+ */
402
+ async getPublish(localParams: LocalParams, clientInfo?: ClientInfo): Promise<PublishObject> {
403
+ const publishParams: PublishParams = await this.getPublishParams(localParams, clientInfo)
404
+ let _publish = await applyParamsIfFunc(this.publish, publishParams );
405
+
406
+ if(_publish === "*"){
407
+ let publish = {} as any;
408
+ this.prostgles.dboBuilder.tablesOrViews?.map(tov => {
409
+ publish[tov.name] = "*";
410
+ });
411
+ return publish;
412
+ }
413
+
414
+ return _publish;
415
+ }
416
+ async getValidatedRequestRuleWusr({ tableName, command, localParams }: DboTableCommand): Promise<TableRule>{
417
+ const clientInfo = await this.prostgles.authHandler!.getClientInfo(localParams);
418
+ return await this.getValidatedRequestRule({ tableName, command, localParams }, clientInfo);
419
+ }
420
+
421
+ async getValidatedRequestRule({ tableName, command, localParams }: DboTableCommand, clientInfo?: ClientInfo): Promise<TableRule>{
422
+ if(!this.dbo) throw "INTERNAL ERROR: dbo is missing";
423
+
424
+ if(!command || !tableName) throw "command OR tableName are missing";
425
+
426
+ let rtm = RULE_TO_METHODS.find(rtms => (rtms.methods as any).includes(command));
427
+ if(!rtm){
428
+ throw "Invalid command: " + command;
429
+ }
430
+
431
+ /* Must be local request -> allow everything */
432
+ if(!localParams || (!localParams.socket && !localParams.httpReq)){
433
+ return RULE_TO_METHODS.reduce((a, v) => ({
434
+ ...a,
435
+ [v.rule]: v.no_limits
436
+ }), {})
437
+ }
438
+
439
+ /* Must be from socket. Must have a publish */
440
+ if(!this.publish) throw "publish is missing";
441
+
442
+ /* Get any publish errors for socket */
443
+ const schm = localParams?.socket?.prostgles?.schema?.[tableName]?.[command];
444
+
445
+ if(schm && schm.err) throw schm.err;
446
+
447
+ let table_rule = await this.getTableRules({ tableName, localParams }, clientInfo);
448
+ if(!table_rule) throw "Invalid or disallowed table: " + tableName;
449
+
450
+
451
+ if(command === "upsert"){
452
+ if(!table_rule.update || !table_rule.insert){
453
+ throw `Invalid or disallowed command: upsert`;
454
+ }
455
+ }
456
+
457
+ if(rtm && table_rule && table_rule[rtm.rule]){
458
+ return table_rule;
459
+ } else throw `Invalid or disallowed command: ${tableName}.${command}`;
460
+ }
461
+
462
+ async getTableRules({ tableName, localParams }: DboTable, clientInfo?: ClientInfo): Promise<ParsedPublishTable | undefined> {
463
+
464
+ try {
465
+ if(!localParams || !tableName) throw "publish OR socket OR dbo OR tableName are missing";
466
+
467
+ let _publish = await this.getPublish(localParams, clientInfo);
468
+
469
+ const raw_table_rules = _publish[tableName];// applyParamsIfFunc(_publish[tableName], localParams, this.dbo, this.db, user);
470
+ if(!raw_table_rules) return undefined;
471
+
472
+ let parsed_table: ParsedPublishTable = {};
473
+
474
+ /* Get view or table specific rules */
475
+ const tHandler = (this.dbo[tableName] as TableHandler | ViewHandler);
476
+
477
+ if(!tHandler) throw `${tableName} could not be found in dbo`;
478
+
479
+ const is_view = tHandler.is_view;
480
+ const MY_RULES = RULE_TO_METHODS.filter(r => {
481
+
482
+ /** Check PG User privileges */
483
+ const pgUserIsAllowedThis = tHandler.tableOrViewInfo.privileges[r.sqlRule];
484
+ const result = (!is_view || !r.table_only) && pgUserIsAllowedThis;
485
+
486
+ if(!pgUserIsAllowedThis && isPlainObject(raw_table_rules) && (raw_table_rules as PublishTableRule)[r.sqlRule]){
487
+ throw `Your postgres user is not allowed ${r.sqlRule} on table ${tableName}`;
488
+ }
489
+ return result;
490
+ });
491
+
492
+
493
+
494
+ /* All methods allowed. Add no limits for table rules */
495
+ if([true, "*"].includes(raw_table_rules as any)){
496
+ parsed_table = {};
497
+ MY_RULES.map(r => {
498
+ parsed_table[r.rule] = { ...r.no_limits as object } as any;
499
+ });
500
+
501
+ /** Specific rules allowed */
502
+ } else if(isPlainObject(raw_table_rules) && getKeys(raw_table_rules).length){
503
+ const allRuleKeys: (keyof PublishViewRule | keyof PublishTableRule)[] = getKeys(raw_table_rules);
504
+ const dissallowedRuleKeys = allRuleKeys.filter(m => !(raw_table_rules as PublishTableRule)[m])
505
+
506
+ MY_RULES.map(r => {
507
+ /** Unless specifically disabled these are allowed */
508
+ if(["getInfo", "getColumns"].includes(r.rule) && !dissallowedRuleKeys.includes(r.rule as any)){
509
+ parsed_table[r.rule] = r.no_limits as any;
510
+ return ;
511
+ }
512
+
513
+ /** Add no_limit values for implied/ fully allowed methods */
514
+ if ([true, "*"].includes((raw_table_rules as PublishTableRule)[r.rule] as any) && r.no_limits) {
515
+ parsed_table[r.rule] = Object.assign({}, r.no_limits) as any;
516
+
517
+ /** Carry over detailed config */
518
+ } else if(isPlainObject((raw_table_rules as any)[r.rule])){
519
+ parsed_table[r.rule] = (raw_table_rules as any)[r.rule]
520
+ }
521
+ });
522
+
523
+ allRuleKeys.filter(m => parsed_table[m])
524
+ .find((method) => {
525
+ let rm = MY_RULES.find(r => r.rule === method || (r.methods as readonly string[]).includes(method));
526
+ if(!rm){
527
+ let extraInfo = "";
528
+ if(is_view && RULE_TO_METHODS.find(r => !is_view && r.rule === method || (r.methods as any).includes(method))){
529
+ extraInfo = "You've specified table rules to a view\n";
530
+ }
531
+ throw `Invalid rule in publish.${tableName} -> ${method} \n${extraInfo}Expecting any of: ${MY_RULES.flatMap(r => [r.rule, ...r.methods]).join(", ")}`;
532
+ }
533
+
534
+ /* Check RULES for invalid params */
535
+ /* Methods do not have params -> They use them from rules */
536
+ if(method === rm.rule){
537
+ let method_params = getKeys(parsed_table[method]);
538
+ let iparam = method_params.find(p => !rm?.allowed_params.includes(<never>p));
539
+ if(iparam){
540
+ throw `Invalid setting in publish.${tableName}.${method} -> ${iparam}. \n Expecting any of: ${rm.allowed_params.join(", ")}`;
541
+ }
542
+ }
543
+
544
+ /* Add default params (if missing) */
545
+ if(method === "sync"){
546
+
547
+ if([true, "*"].includes(parsed_table[method] as any)){
548
+ throw "Invalid sync rule. Expecting { id_fields: string[], synced_field: string } ";
549
+ }
550
+
551
+ if(typeof parsed_table[method]?.throttle !== "number"){
552
+ parsed_table[method]!.throttle = 100;
553
+ }
554
+ if(typeof parsed_table[method]?.batch_size !== "number"){
555
+ parsed_table[method]!.batch_size = DEFAULT_SYNC_BATCH_SIZE;
556
+ }
557
+ }
558
+
559
+ /* Enable subscribe if not explicitly disabled */
560
+ const subKey = "subscribe" as const;
561
+
562
+ if(method === "select" && !dissallowedRuleKeys.includes(subKey)){
563
+ const sr = MY_RULES.find(r => r.rule === subKey);
564
+ if(sr){
565
+ parsed_table[subKey] = { ...sr.no_limits as SubscribeRule };
566
+ parsed_table.subscribeOne = { ...sr.no_limits as SubscribeRule };
567
+ }
568
+ }
569
+ });
570
+
571
+ } else {
572
+ throw "Unexpected publish"
573
+ }
574
+
575
+ const getImpliedMethods = (tableRules: ParsedPublishTable): ParsedPublishTable => {
576
+ let res = { ...tableRules };
577
+
578
+ /* Add implied methods if not specifically dissallowed */
579
+ MY_RULES.map(r => {
580
+
581
+ /** THIS IS A MESS -> some methods cannot be dissallowed (unsync, unsubscribe...) */
582
+ r.methods.forEach(method => {
583
+ const isAllowed = tableRules[r.rule] && (tableRules as any)[method] === undefined;
584
+ if(isAllowed){
585
+
586
+ if(method === "updateBatch" && !tableRules.update){
587
+
588
+ } else if(method === "upsert" && (!tableRules.update || !tableRules.insert)){
589
+
590
+ } else {
591
+ (res as any)[method] ??= true;
592
+ }
593
+ }
594
+ });
595
+ });
596
+
597
+ return res;
598
+ }
599
+
600
+ parsed_table = getImpliedMethods(parsed_table);
601
+
602
+ return parsed_table;
603
+ } catch (e) {
604
+ throw e;
605
+ }
606
+ }
607
+
608
+
609
+
610
+ /* Prepares schema for client. Only allowed views and commands will be present */
611
+ async getSchemaFromPublish(socket: any): Promise<{schema: TableSchemaForClient; tables: DBSchemaTable[] }> {
612
+ let schema: TableSchemaForClient = {};
613
+ let tables: DBSchemaTable[] = []
614
+
615
+ try {
616
+ /* Publish tables and views based on socket */
617
+ const clientInfo = await this.prostgles.authHandler?.getClientInfo({ socket });
618
+ let _publish = await this.getPublish(socket, clientInfo);
619
+
620
+
621
+ if(_publish && Object.keys(_publish).length){
622
+ let txKey = "tx";
623
+ if(!this.prostgles.opts.transactions) txKey = "";
624
+ if(typeof this.prostgles.opts.transactions === "string") txKey = this.prostgles.opts.transactions;
625
+
626
+ const tableNames = Object.keys(_publish).filter(k => !txKey || txKey !== k);
627
+
628
+ await Promise.all(tableNames
629
+ .map(async tableName => {
630
+ if(!this.dbo[tableName]) {
631
+ throw `Table ${tableName} does not exist
632
+ Expecting one of: ${this.prostgles.dboBuilder.tablesOrViews?.map(tov => tov.name).join(", ")}
633
+ DBO tables: ${Object.keys(this.dbo).filter(k => (this.dbo[k] as any).find).join(", ")}
634
+ `;
635
+ }
636
+
637
+ const table_rules = await this.getTableRules({ localParams: {socket}, tableName }, clientInfo);
638
+
639
+ if(table_rules && Object.keys(table_rules).length){
640
+ schema[tableName] = {};
641
+ let methods: MethodKey[] = [];
642
+ let tableInfo: TableInfo | undefined;
643
+ let tableColumns: DBSchemaTable["columns"] | undefined;
644
+
645
+ if(typeof table_rules === "object"){
646
+ methods = getKeys(table_rules) as any;
647
+ }
648
+
649
+ await Promise.all(methods.filter(m => m !== "select" as any).map(async method => {
650
+ if(method === "sync" && table_rules[method]){
651
+
652
+ /* Pass sync info */
653
+ schema[tableName][method] = table_rules[method];
654
+ } else if((table_rules as any)[method]) {
655
+
656
+ schema[tableName][method] = {};
657
+
658
+ /* Test for issues with the common table CRUD methods () */
659
+ if(TABLE_METHODS.includes(method as any)){
660
+
661
+ let err = null;
662
+ try {
663
+ let valid_table_command_rules = await this.getValidatedRequestRule({ tableName, command: method, localParams: {socket} }, clientInfo);
664
+ await (this.dbo[tableName] as any)[method]({}, {}, {}, valid_table_command_rules, { socket, has_rules: true, testRule: true });
665
+
666
+ } catch(e) {
667
+ err = "INTERNAL PUBLISH ERROR";
668
+ schema[tableName][method] = { err };
669
+
670
+ throw `publish.${tableName}.${method}: \n -> ${e}`;
671
+ }
672
+ }
673
+
674
+
675
+ if(method === "getInfo" || method === "getColumns"){
676
+ let tableRules = await this.getValidatedRequestRule({ tableName, command: method, localParams: {socket} }, clientInfo);
677
+ const res = await (this.dbo[tableName] as any)[method](undefined, undefined, undefined , tableRules, { socket, has_rules: true });
678
+ if(method === "getInfo"){
679
+ tableInfo = res;
680
+ } else if(method === "getColumns"){
681
+ tableColumns = res;
682
+ }
683
+ }
684
+ }
685
+ }));
686
+
687
+ if(tableInfo && tableColumns){
688
+
689
+ tables.push({
690
+ name: tableName,
691
+ info: tableInfo,
692
+ columns: tableColumns
693
+ })
694
+ }
695
+ }
696
+
697
+ return true;
698
+ })
699
+ );
700
+ }
701
+
702
+
703
+ } catch (e) {
704
+ console.error("Prostgles \nERRORS IN PUBLISH: ", JSON.stringify(e));
705
+ throw e;
706
+ }
707
+
708
+ return { schema, tables };
709
+ }
710
+
711
+ }
712
+
713
+
714
+ function applyParamsIfFunc(maybeFunc: any, ...params: any): any{
715
+ if(
716
+ (maybeFunc !== null && maybeFunc !== undefined) &&
717
+ (typeof maybeFunc === "function" || typeof maybeFunc.then === "function")
718
+ ){
719
+ return maybeFunc(...params);
720
+ }
721
+
722
+ return maybeFunc;
723
+ }