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.
- package/.vscode/settings.json +3 -0
- package/dist/AuthHandler.d.ts +13 -12
- package/dist/AuthHandler.d.ts.map +1 -1
- package/dist/AuthHandler.js +5 -2
- package/dist/AuthHandler.js.map +1 -1
- package/dist/DBSchemaBuilder.d.ts +11 -0
- package/dist/DBSchemaBuilder.d.ts.map +1 -0
- package/dist/DBSchemaBuilder.js +56 -0
- package/dist/DBSchemaBuilder.js.map +1 -0
- package/dist/DboBuilder.d.ts +23 -22
- package/dist/DboBuilder.d.ts.map +1 -1
- package/dist/DboBuilder.js +36 -61
- package/dist/DboBuilder.js.map +1 -1
- package/dist/FileManager.d.ts +2 -2
- package/dist/FileManager.d.ts.map +1 -1
- package/dist/Filtering.d.ts.map +1 -1
- package/dist/Filtering.js.map +1 -1
- package/dist/Prostgles.d.ts +25 -257
- package/dist/Prostgles.d.ts.map +1 -1
- package/dist/Prostgles.js +12 -376
- package/dist/Prostgles.js.map +1 -1
- package/dist/PubSubManager.d.ts +6 -5
- package/dist/PubSubManager.d.ts.map +1 -1
- package/dist/PubSubManager.js.map +1 -1
- package/dist/PublishParser.d.ts +262 -0
- package/dist/PublishParser.d.ts.map +1 -0
- package/dist/PublishParser.js +391 -0
- package/dist/PublishParser.js.map +1 -0
- package/dist/QueryBuilder.d.ts +20 -4
- package/dist/QueryBuilder.d.ts.map +1 -1
- package/dist/QueryBuilder.js.map +1 -1
- package/dist/TableConfig.d.ts +6 -3
- package/dist/TableConfig.d.ts.map +1 -1
- package/dist/TableConfig.js +28 -1
- package/dist/TableConfig.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/lib/AuthHandler.ts +25 -19
- package/lib/DBSchemaBuilder.ts +91 -0
- package/lib/DboBuilder.ts +84 -98
- package/lib/FileManager.ts +2 -2
- package/lib/Filtering.ts +3 -3
- package/lib/Prostgles.ts +33 -704
- package/lib/PubSubManager.ts +6 -5
- package/lib/PublishParser.ts +723 -0
- package/lib/QueryBuilder.ts +6 -5
- package/lib/TableConfig.ts +36 -5
- package/lib/index.ts +4 -4
- package/package.json +2 -2
- package/tests/client/PID.txt +1 -1
- package/tests/client/index.js +2 -2
- package/tests/client/index.ts +2 -2
- package/tests/client/package-lock.json +15 -15
- package/tests/client/package.json +1 -1
- package/tests/client_only_queries.js +24 -1
- package/tests/client_only_queries.ts +23 -1
- package/tests/isomorphic_queries.js +3 -0
- package/tests/isomorphic_queries.ts +5 -2
- package/tests/server/DBoGenerated.d.ts +428 -286
- package/tests/server/index.js +1 -14
- package/tests/server/index.ts +5 -19
- 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
|
+
}
|