@snowtop/ent 0.0.37 → 0.0.39-alpha14
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/action/action.d.ts +2 -0
- package/action/orchestrator.d.ts +7 -1
- package/action/orchestrator.js +108 -28
- package/core/base.d.ts +3 -0
- package/core/clause.d.ts +24 -3
- package/core/clause.js +246 -5
- package/core/config.d.ts +18 -0
- package/core/config.js +17 -0
- package/core/ent.d.ts +2 -4
- package/core/ent.js +54 -22
- package/core/loaders/index_loader.js +1 -0
- package/core/loaders/object_loader.d.ts +1 -0
- package/core/loaders/object_loader.js +22 -4
- package/graphql/graphql.d.ts +3 -2
- package/graphql/graphql.js +22 -21
- package/graphql/node_resolver.d.ts +0 -1
- package/index.d.ts +16 -1
- package/index.js +17 -4
- package/package.json +2 -2
- package/parse_schema/parse.d.ts +9 -2
- package/parse_schema/parse.js +22 -1
- package/schema/index.d.ts +1 -1
- package/schema/index.js +4 -1
- package/schema/schema.d.ts +43 -1
- package/schema/schema.js +78 -5
- package/scripts/custom_graphql.js +122 -15
- package/testutils/builder.js +5 -0
package/action/action.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Ent, EntConstructor, Viewer, ID, Data, PrivacyPolicy, Context } from "../core/base";
|
|
2
2
|
import { DataOperation, AssocEdgeInputOptions } from "../core/ent";
|
|
3
3
|
import { Queryer } from "../core/db";
|
|
4
|
+
import { TransformedUpdateOperation, UpdateOperation } from "../schema";
|
|
4
5
|
export declare enum WriteOperation {
|
|
5
6
|
Insert = "insert",
|
|
6
7
|
Edit = "edit",
|
|
@@ -50,6 +51,7 @@ export interface Action<T extends Ent> {
|
|
|
50
51
|
observers?: Observer<T>[];
|
|
51
52
|
validators?: Validator<T>[];
|
|
52
53
|
getInput(): Data;
|
|
54
|
+
transformWrite?: <T2 extends Ent>(stmt: UpdateOperation<T2>) => Promise<TransformedUpdateOperation<T2>> | TransformedUpdateOperation<T2> | undefined;
|
|
53
55
|
valid(): Promise<boolean>;
|
|
54
56
|
validX(): Promise<void>;
|
|
55
57
|
viewerForEntLoad?(data: Data): Viewer | Promise<Viewer>;
|
package/action/orchestrator.d.ts
CHANGED
|
@@ -12,7 +12,7 @@ export interface OrchestratorOptions<T extends Ent, TData extends Data> {
|
|
|
12
12
|
builder: Builder<T>;
|
|
13
13
|
action?: Action<T>;
|
|
14
14
|
schema: SchemaInputType;
|
|
15
|
-
editedFields(): Map<string, any
|
|
15
|
+
editedFields(): Map<string, any> | Promise<Map<string, any>>;
|
|
16
16
|
updateInput?: (data: TData) => void;
|
|
17
17
|
}
|
|
18
18
|
interface edgeInputDataOpts {
|
|
@@ -41,8 +41,12 @@ export declare class Orchestrator<T extends Ent> {
|
|
|
41
41
|
viewer: Viewer;
|
|
42
42
|
private defaultFieldsByFieldName;
|
|
43
43
|
private defaultFieldsByTSName;
|
|
44
|
+
private actualOperation;
|
|
45
|
+
private existingEnt?;
|
|
46
|
+
private disableTransformations;
|
|
44
47
|
constructor(options: OrchestratorOptions<T, Data>);
|
|
45
48
|
private addEdge;
|
|
49
|
+
setDisableTransformations(val: boolean): void;
|
|
46
50
|
addInboundEdge<T2 extends Ent>(id1: ID | Builder<T2>, edgeType: string, nodeType: string, options?: AssocEdgeInputOptions): void;
|
|
47
51
|
addOutboundEdge<T2 extends Ent>(id2: ID | Builder<T2>, edgeType: string, nodeType: string, options?: AssocEdgeInputOptions): void;
|
|
48
52
|
removeInboundEdge(id1: ID, edgeType: string): void;
|
|
@@ -54,6 +58,8 @@ export declare class Orchestrator<T extends Ent> {
|
|
|
54
58
|
private buildEdgeOps;
|
|
55
59
|
private throwError;
|
|
56
60
|
private getEntForPrivacyPolicy;
|
|
61
|
+
private getSQLStatementOperation;
|
|
62
|
+
private getWriteOpForSQLStamentOp;
|
|
57
63
|
private validate;
|
|
58
64
|
private triggers;
|
|
59
65
|
private validators;
|
package/action/orchestrator.js
CHANGED
|
@@ -4,7 +4,6 @@ exports.EntChangeset = exports.Orchestrator = exports.edgeDirection = void 0;
|
|
|
4
4
|
const ent_1 = require("../core/ent");
|
|
5
5
|
const schema_1 = require("../schema/schema");
|
|
6
6
|
const action_1 = require("../action");
|
|
7
|
-
const snake_case_1 = require("snake-case");
|
|
8
7
|
const camel_case_1 = require("camel-case");
|
|
9
8
|
const privacy_1 = require("../core/privacy");
|
|
10
9
|
const executor_1 = require("./executor");
|
|
@@ -62,6 +61,8 @@ class Orchestrator {
|
|
|
62
61
|
this.defaultFieldsByFieldName = {};
|
|
63
62
|
this.defaultFieldsByTSName = {};
|
|
64
63
|
this.viewer = options.viewer;
|
|
64
|
+
this.actualOperation = this.options.operation;
|
|
65
|
+
this.existingEnt = this.options.builder.existingEnt;
|
|
65
66
|
}
|
|
66
67
|
addEdge(edge, op) {
|
|
67
68
|
this.edgeSet.add(edge.edgeType);
|
|
@@ -80,6 +81,9 @@ class Orchestrator {
|
|
|
80
81
|
m1.set(op, m2);
|
|
81
82
|
this.edges.set(edge.edgeType, m1);
|
|
82
83
|
}
|
|
84
|
+
setDisableTransformations(val) {
|
|
85
|
+
this.disableTransformations = val;
|
|
86
|
+
}
|
|
83
87
|
addInboundEdge(id1, edgeType, nodeType, options) {
|
|
84
88
|
this.addEdge(new edgeInputData({
|
|
85
89
|
id: id1,
|
|
@@ -135,24 +139,27 @@ class Orchestrator {
|
|
|
135
139
|
}
|
|
136
140
|
buildMainOp() {
|
|
137
141
|
// this assumes we have validated fields
|
|
138
|
-
switch (this.
|
|
142
|
+
switch (this.actualOperation) {
|
|
139
143
|
case action_1.WriteOperation.Delete:
|
|
140
|
-
return new ent_1.DeleteNodeOperation(this.
|
|
144
|
+
return new ent_1.DeleteNodeOperation(this.existingEnt.id, {
|
|
141
145
|
tableName: this.options.tableName,
|
|
142
146
|
});
|
|
143
147
|
default:
|
|
148
|
+
if (this.actualOperation === action_1.WriteOperation.Edit && !this.existingEnt) {
|
|
149
|
+
throw new Error(`existing ent required with operation ${this.actualOperation}`);
|
|
150
|
+
}
|
|
144
151
|
const opts = {
|
|
145
152
|
fields: this.validatedFields,
|
|
146
153
|
tableName: this.options.tableName,
|
|
147
154
|
fieldsToResolve: this.fieldsToResolve,
|
|
148
155
|
key: this.options.key,
|
|
149
|
-
|
|
156
|
+
loadEntOptions: this.options.loaderOptions,
|
|
150
157
|
placeholderID: this.options.builder.placeholderID,
|
|
151
158
|
};
|
|
152
159
|
if (this.logValues) {
|
|
153
160
|
opts.fieldsToLog = this.logValues;
|
|
154
161
|
}
|
|
155
|
-
this.mainOp = new ent_1.EditNodeOperation(opts, this.
|
|
162
|
+
this.mainOp = new ent_1.EditNodeOperation(opts, this.existingEnt);
|
|
156
163
|
return this.mainOp;
|
|
157
164
|
}
|
|
158
165
|
}
|
|
@@ -219,35 +226,58 @@ class Orchestrator {
|
|
|
219
226
|
if (!privacyPolicy || !action) {
|
|
220
227
|
throw new Error(`shouldn't get here if no privacyPolicy for action`);
|
|
221
228
|
}
|
|
222
|
-
if (this.
|
|
229
|
+
if (this.actualOperation === action_1.WriteOperation.Insert) {
|
|
223
230
|
return new EntCannotCreateEntError(privacyPolicy, action);
|
|
224
231
|
}
|
|
225
|
-
else if (this.
|
|
226
|
-
return new EntCannotEditEntError(privacyPolicy, action, this.
|
|
232
|
+
else if (this.actualOperation === action_1.WriteOperation.Edit) {
|
|
233
|
+
return new EntCannotEditEntError(privacyPolicy, action, this.existingEnt);
|
|
227
234
|
}
|
|
228
|
-
return new EntCannotDeleteEntError(privacyPolicy, action, this.
|
|
235
|
+
return new EntCannotDeleteEntError(privacyPolicy, action, this.existingEnt);
|
|
229
236
|
}
|
|
230
237
|
getEntForPrivacyPolicy(editedData) {
|
|
231
|
-
if (this.
|
|
232
|
-
return this.
|
|
238
|
+
if (this.actualOperation !== action_1.WriteOperation.Insert) {
|
|
239
|
+
return this.existingEnt;
|
|
233
240
|
}
|
|
234
241
|
// we create an unsafe ent to be used for privacy policies
|
|
235
242
|
return new this.options.builder.ent(this.options.builder.viewer, editedData);
|
|
236
243
|
}
|
|
244
|
+
getSQLStatementOperation() {
|
|
245
|
+
switch (this.actualOperation) {
|
|
246
|
+
case action_1.WriteOperation.Edit:
|
|
247
|
+
return schema_1.SQLStatementOperation.Update;
|
|
248
|
+
case action_1.WriteOperation.Insert:
|
|
249
|
+
return schema_1.SQLStatementOperation.Insert;
|
|
250
|
+
case action_1.WriteOperation.Delete:
|
|
251
|
+
return schema_1.SQLStatementOperation.Delete;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
getWriteOpForSQLStamentOp(op) {
|
|
255
|
+
switch (op) {
|
|
256
|
+
case schema_1.SQLStatementOperation.Update:
|
|
257
|
+
return action_1.WriteOperation.Edit;
|
|
258
|
+
case schema_1.SQLStatementOperation.Insert:
|
|
259
|
+
return action_1.WriteOperation.Insert;
|
|
260
|
+
case schema_1.SQLStatementOperation.Update:
|
|
261
|
+
return action_1.WriteOperation.Delete;
|
|
262
|
+
default:
|
|
263
|
+
throw new Error("invalid path");
|
|
264
|
+
}
|
|
265
|
+
}
|
|
237
266
|
async validate() {
|
|
238
267
|
// existing ent required for edit or delete operations
|
|
239
|
-
switch (this.
|
|
268
|
+
switch (this.actualOperation) {
|
|
240
269
|
case action_1.WriteOperation.Delete:
|
|
241
270
|
case action_1.WriteOperation.Edit:
|
|
242
|
-
if (!this.
|
|
243
|
-
throw new Error(
|
|
271
|
+
if (!this.existingEnt) {
|
|
272
|
+
throw new Error(`existing ent required with operation ${this.actualOperation}`);
|
|
244
273
|
}
|
|
245
274
|
}
|
|
246
275
|
const action = this.options.action;
|
|
247
276
|
const builder = this.options.builder;
|
|
248
277
|
// future optimization: can get schemaFields to memoize based on different values
|
|
249
278
|
const schemaFields = (0, schema_1.getFields)(this.options.schema);
|
|
250
|
-
|
|
279
|
+
const editedFields = await this.options.editedFields();
|
|
280
|
+
let editedData = await this.getFieldsWithDefaultValues(builder, schemaFields, editedFields, action);
|
|
251
281
|
// this runs in following phases:
|
|
252
282
|
// * set default fields and pass to builder so the value can be checked by triggers/observers/validators
|
|
253
283
|
// * privacy policy (use unsafe ent if we have it)
|
|
@@ -264,8 +294,12 @@ class Orchestrator {
|
|
|
264
294
|
await this.triggers(action, builder, triggers);
|
|
265
295
|
}
|
|
266
296
|
let validators = action?.validators || [];
|
|
297
|
+
// TODO not ideal we're calling this twice. fix...
|
|
298
|
+
// TODO add core test for softe delete with soft_delete package duplicated?
|
|
299
|
+
// to confirm it works and things like this don't break it.
|
|
300
|
+
const editedFields2 = await this.options.editedFields();
|
|
267
301
|
await Promise.all([
|
|
268
|
-
this.formatAndValidateFields(schemaFields),
|
|
302
|
+
this.formatAndValidateFields(schemaFields, editedFields2),
|
|
269
303
|
this.validators(validators, action, builder),
|
|
270
304
|
]);
|
|
271
305
|
}
|
|
@@ -300,18 +334,65 @@ class Orchestrator {
|
|
|
300
334
|
isBuilder(val) {
|
|
301
335
|
return val.placeholderID !== undefined;
|
|
302
336
|
}
|
|
303
|
-
getFieldsWithDefaultValues(builder, schemaFields, action) {
|
|
304
|
-
const editedFields = this.options.editedFields();
|
|
337
|
+
async getFieldsWithDefaultValues(builder, schemaFields, editedFields, action) {
|
|
305
338
|
let data = {};
|
|
306
339
|
let defaultData = {};
|
|
307
340
|
let input = action?.getInput() || {};
|
|
308
341
|
let updateInput = false;
|
|
342
|
+
// transformations
|
|
343
|
+
// if action transformations. always do it
|
|
344
|
+
// if disable transformations set, don't do schema transform and just do the right thing
|
|
345
|
+
// else apply schema tranformation if it exists
|
|
346
|
+
let transformed;
|
|
347
|
+
if (action?.transformWrite) {
|
|
348
|
+
transformed = await action.transformWrite({
|
|
349
|
+
viewer: builder.viewer,
|
|
350
|
+
op: this.getSQLStatementOperation(),
|
|
351
|
+
data: editedFields,
|
|
352
|
+
existingEnt: this.existingEnt,
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
else if (!this.disableTransformations) {
|
|
356
|
+
transformed = (0, schema_1.getTransformedUpdateOp)(this.options.schema, {
|
|
357
|
+
viewer: builder.viewer,
|
|
358
|
+
op: this.getSQLStatementOperation(),
|
|
359
|
+
data: editedFields,
|
|
360
|
+
existingEnt: this.existingEnt,
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
if (transformed) {
|
|
364
|
+
if (transformed.data) {
|
|
365
|
+
updateInput = true;
|
|
366
|
+
for (const k in transformed.data) {
|
|
367
|
+
let field = schemaFields.get(k);
|
|
368
|
+
if (!field) {
|
|
369
|
+
throw new Error(`tried to transform field with unknown field ${k}`);
|
|
370
|
+
}
|
|
371
|
+
let val = transformed.data[k];
|
|
372
|
+
if (field.format) {
|
|
373
|
+
val = field.format(transformed.data[k]);
|
|
374
|
+
}
|
|
375
|
+
let dbKey = (0, schema_1.getStorageKey)(field);
|
|
376
|
+
data[dbKey] = val;
|
|
377
|
+
this.defaultFieldsByTSName[(0, camel_case_1.camelCase)(k)] = val;
|
|
378
|
+
// hmm do we need this?
|
|
379
|
+
// TODO how to do this for local tests?
|
|
380
|
+
// this.defaultFieldsByFieldName[k] = val;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
this.actualOperation = this.getWriteOpForSQLStamentOp(transformed.op);
|
|
384
|
+
if (transformed.existingEnt) {
|
|
385
|
+
this.existingEnt = transformed.existingEnt;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
// transforming before doing default fields so that we don't create a new id
|
|
389
|
+
// and anything that depends on the type of operations knows what it is
|
|
309
390
|
for (const [fieldName, field] of schemaFields) {
|
|
310
391
|
let value = editedFields.get(fieldName);
|
|
311
392
|
let defaultValue = undefined;
|
|
312
|
-
let dbKey =
|
|
393
|
+
let dbKey = (0, schema_1.getStorageKey)(field);
|
|
313
394
|
if (value === undefined) {
|
|
314
|
-
if (this.
|
|
395
|
+
if (this.actualOperation === action_1.WriteOperation.Insert) {
|
|
315
396
|
if (field.defaultToViewerOnCreate && field.defaultValueOnCreate) {
|
|
316
397
|
throw new Error(`cannot set both defaultToViewerOnCreate and defaultValueOnCreate`);
|
|
317
398
|
}
|
|
@@ -326,7 +407,7 @@ class Orchestrator {
|
|
|
326
407
|
}
|
|
327
408
|
}
|
|
328
409
|
if (field.defaultValueOnEdit &&
|
|
329
|
-
this.
|
|
410
|
+
this.actualOperation === action_1.WriteOperation.Edit) {
|
|
330
411
|
defaultValue = field.defaultValueOnEdit(builder, input);
|
|
331
412
|
}
|
|
332
413
|
}
|
|
@@ -374,7 +455,7 @@ class Orchestrator {
|
|
|
374
455
|
// not setting server default as we're depending on the database handling that.
|
|
375
456
|
// server default allowed
|
|
376
457
|
field.serverDefault === undefined &&
|
|
377
|
-
this.
|
|
458
|
+
this.actualOperation === action_1.WriteOperation.Insert) {
|
|
378
459
|
throw new Error(`required field ${field.name} not set`);
|
|
379
460
|
}
|
|
380
461
|
}
|
|
@@ -405,12 +486,11 @@ class Orchestrator {
|
|
|
405
486
|
}
|
|
406
487
|
return value;
|
|
407
488
|
}
|
|
408
|
-
async formatAndValidateFields(schemaFields) {
|
|
409
|
-
const op = this.
|
|
489
|
+
async formatAndValidateFields(schemaFields, editedFields) {
|
|
490
|
+
const op = this.actualOperation;
|
|
410
491
|
if (op === action_1.WriteOperation.Delete) {
|
|
411
492
|
return;
|
|
412
493
|
}
|
|
413
|
-
const editedFields = this.options.editedFields();
|
|
414
494
|
// build up data to be saved...
|
|
415
495
|
let data = {};
|
|
416
496
|
let logValues = {};
|
|
@@ -420,7 +500,7 @@ class Orchestrator {
|
|
|
420
500
|
// null allowed
|
|
421
501
|
value = this.defaultFieldsByFieldName[fieldName];
|
|
422
502
|
}
|
|
423
|
-
let dbKey =
|
|
503
|
+
let dbKey = (0, schema_1.getStorageKey)(field);
|
|
424
504
|
value = await this.transformFieldValue(field, dbKey, value);
|
|
425
505
|
if (value !== undefined) {
|
|
426
506
|
data[dbKey] = value;
|
|
@@ -433,7 +513,7 @@ class Orchestrator {
|
|
|
433
513
|
for (const fieldName in this.defaultFieldsByFieldName) {
|
|
434
514
|
const defaultValue = this.defaultFieldsByFieldName[fieldName];
|
|
435
515
|
let field = schemaFields.get(fieldName);
|
|
436
|
-
let dbKey =
|
|
516
|
+
let dbKey = (0, schema_1.getStorageKey)(field);
|
|
437
517
|
// no value, let's just default
|
|
438
518
|
if (data[dbKey] === undefined) {
|
|
439
519
|
const value = await this.transformFieldValue(field, dbKey, defaultValue);
|
|
@@ -494,7 +574,7 @@ class Orchestrator {
|
|
|
494
574
|
const viewer = await this.viewerForEntLoad(row);
|
|
495
575
|
const ent = await (0, ent_1.applyPrivacyPolicyForRow)(viewer, this.options.loaderOptions, row);
|
|
496
576
|
if (!ent) {
|
|
497
|
-
if (this.
|
|
577
|
+
if (this.actualOperation == action_1.WriteOperation.Insert) {
|
|
498
578
|
throw new Error(`was able to create ent but not load it`);
|
|
499
579
|
}
|
|
500
580
|
else {
|
package/core/base.d.ts
CHANGED
|
@@ -62,6 +62,7 @@ export interface SelectBaseDataOptions extends DataOptions {
|
|
|
62
62
|
}
|
|
63
63
|
export interface SelectDataOptions extends SelectBaseDataOptions {
|
|
64
64
|
key: string;
|
|
65
|
+
clause?: clause.Clause;
|
|
65
66
|
}
|
|
66
67
|
export interface QueryableDataOptions extends SelectBaseDataOptions, QueryDataOptions {
|
|
67
68
|
}
|
|
@@ -88,9 +89,11 @@ interface LoadableEntOptions<T extends Ent> {
|
|
|
88
89
|
ent: EntConstructor<T>;
|
|
89
90
|
}
|
|
90
91
|
export interface LoadEntOptions<T extends Ent> extends LoadableEntOptions<T>, SelectBaseDataOptions {
|
|
92
|
+
fieldPrivacy?: Map<string, PrivacyPolicy>;
|
|
91
93
|
}
|
|
92
94
|
export interface LoadCustomEntOptions<T extends Ent> extends SelectBaseDataOptions {
|
|
93
95
|
ent: EntConstructor<T>;
|
|
96
|
+
fieldPrivacy?: Map<string, PrivacyPolicy>;
|
|
94
97
|
}
|
|
95
98
|
export interface LoaderInfo {
|
|
96
99
|
tableName: string;
|
package/core/clause.d.ts
CHANGED
|
@@ -12,8 +12,10 @@ declare class simpleClause implements Clause {
|
|
|
12
12
|
protected col: string;
|
|
13
13
|
private value;
|
|
14
14
|
private op;
|
|
15
|
-
|
|
15
|
+
private handleSqliteNull?;
|
|
16
|
+
constructor(col: string, value: any, op: string, handleSqliteNull?: Clause | undefined);
|
|
16
17
|
clause(idx: number): string;
|
|
18
|
+
private sqliteNull;
|
|
17
19
|
values(): any[];
|
|
18
20
|
logValues(): any[];
|
|
19
21
|
instanceKey(): string;
|
|
@@ -27,14 +29,33 @@ declare class compositeClause implements Clause {
|
|
|
27
29
|
logValues(): any[];
|
|
28
30
|
instanceKey(): string;
|
|
29
31
|
}
|
|
30
|
-
export declare function
|
|
31
|
-
export declare function
|
|
32
|
+
export declare function ArrayEq(col: string, value: any): Clause;
|
|
33
|
+
export declare function ArrayNotEq(col: string, value: any): Clause;
|
|
34
|
+
export declare function ArrayGreater(col: string, value: any): Clause;
|
|
35
|
+
export declare function ArrayLess(col: string, value: any): Clause;
|
|
36
|
+
export declare function ArrayGreaterEq(col: string, value: any): Clause;
|
|
37
|
+
export declare function ArrayLessEq(col: string, value: any): Clause;
|
|
38
|
+
export declare function Eq(col: string, value: any): Clause;
|
|
39
|
+
export declare function NotEq(col: string, value: any): Clause;
|
|
32
40
|
export declare function Greater(col: string, value: any): simpleClause;
|
|
33
41
|
export declare function Less(col: string, value: any): simpleClause;
|
|
34
42
|
export declare function GreaterEq(col: string, value: any): simpleClause;
|
|
35
43
|
export declare function LessEq(col: string, value: any): simpleClause;
|
|
36
44
|
export declare function And(...args: Clause[]): compositeClause;
|
|
45
|
+
export declare function AndOptional(...args: (Clause | undefined)[]): Clause;
|
|
37
46
|
export declare function Or(...args: Clause[]): compositeClause;
|
|
38
47
|
export declare function In(col: string, ...values: any): Clause;
|
|
48
|
+
interface TsQuery {
|
|
49
|
+
language: "english" | "french" | "german" | "simple";
|
|
50
|
+
value: string;
|
|
51
|
+
}
|
|
52
|
+
export declare function TsQuery(col: string, val: string | TsQuery): Clause;
|
|
53
|
+
export declare function PlainToTsQuery(col: string, val: string | TsQuery): Clause;
|
|
54
|
+
export declare function PhraseToTsQuery(col: string, val: string | TsQuery): Clause;
|
|
55
|
+
export declare function WebsearchToTsQuery(col: string, val: string | TsQuery): Clause;
|
|
56
|
+
export declare function TsVectorColTsQuery(col: string, val: string | TsQuery): Clause;
|
|
57
|
+
export declare function TsVectorPlainToTsQuery(col: string, val: string | TsQuery): Clause;
|
|
58
|
+
export declare function TsVectorPhraseToTsQuery(col: string, val: string | TsQuery): Clause;
|
|
59
|
+
export declare function TsVectorWebsearchToTsQuery(col: string, val: string | TsQuery): Clause;
|
|
39
60
|
export declare function sensitiveValue(val: any): SensitiveValue;
|
|
40
61
|
export {};
|
package/core/clause.js
CHANGED
|
@@ -19,10 +19,12 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|
|
19
19
|
return result;
|
|
20
20
|
};
|
|
21
21
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
-
exports.sensitiveValue = exports.In = exports.Or = exports.And = exports.LessEq = exports.GreaterEq = exports.Less = exports.Greater = exports.NotEq = exports.Eq = void 0;
|
|
22
|
+
exports.sensitiveValue = exports.TsVectorWebsearchToTsQuery = exports.TsVectorPhraseToTsQuery = exports.TsVectorPlainToTsQuery = exports.TsVectorColTsQuery = exports.WebsearchToTsQuery = exports.PhraseToTsQuery = exports.PlainToTsQuery = exports.TsQuery = exports.In = exports.Or = exports.AndOptional = exports.And = exports.LessEq = exports.GreaterEq = exports.Less = exports.Greater = exports.NotEq = exports.Eq = exports.ArrayLessEq = exports.ArrayGreaterEq = exports.ArrayLess = exports.ArrayGreater = exports.ArrayNotEq = exports.ArrayEq = void 0;
|
|
23
23
|
const db_1 = __importStar(require("./db"));
|
|
24
24
|
function isSensitive(val) {
|
|
25
|
-
return (
|
|
25
|
+
return (val !== null &&
|
|
26
|
+
typeof val === "object" &&
|
|
27
|
+
val.logValue !== undefined);
|
|
26
28
|
}
|
|
27
29
|
function rawValue(val) {
|
|
28
30
|
if (isSensitive(val)) {
|
|
@@ -31,17 +33,105 @@ function rawValue(val) {
|
|
|
31
33
|
return val;
|
|
32
34
|
}
|
|
33
35
|
class simpleClause {
|
|
34
|
-
constructor(col, value, op) {
|
|
36
|
+
constructor(col, value, op, handleSqliteNull) {
|
|
35
37
|
this.col = col;
|
|
36
38
|
this.value = value;
|
|
37
39
|
this.op = op;
|
|
40
|
+
this.handleSqliteNull = handleSqliteNull;
|
|
38
41
|
}
|
|
39
42
|
clause(idx) {
|
|
43
|
+
const sqliteClause = this.sqliteNull();
|
|
44
|
+
if (sqliteClause) {
|
|
45
|
+
return sqliteClause.clause(idx);
|
|
46
|
+
}
|
|
40
47
|
if (db_1.default.getDialect() === db_1.Dialect.Postgres) {
|
|
41
48
|
return `${this.col} ${this.op} $${idx}`;
|
|
42
49
|
}
|
|
43
50
|
return `${this.col} ${this.op} ?`;
|
|
44
51
|
}
|
|
52
|
+
sqliteNull() {
|
|
53
|
+
if (!this.handleSqliteNull || this.value !== null) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
if (db_1.default.getDialect() !== db_1.Dialect.SQLite) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
return this.handleSqliteNull;
|
|
60
|
+
}
|
|
61
|
+
values() {
|
|
62
|
+
const sqliteClause = this.sqliteNull();
|
|
63
|
+
if (sqliteClause) {
|
|
64
|
+
return sqliteClause.values();
|
|
65
|
+
}
|
|
66
|
+
if (isSensitive(this.value)) {
|
|
67
|
+
return [this.value.value()];
|
|
68
|
+
}
|
|
69
|
+
return [this.value];
|
|
70
|
+
}
|
|
71
|
+
logValues() {
|
|
72
|
+
const sqliteClause = this.sqliteNull();
|
|
73
|
+
if (sqliteClause) {
|
|
74
|
+
return sqliteClause.logValues();
|
|
75
|
+
}
|
|
76
|
+
if (isSensitive(this.value)) {
|
|
77
|
+
return [this.value.logValue()];
|
|
78
|
+
}
|
|
79
|
+
return [this.value];
|
|
80
|
+
}
|
|
81
|
+
instanceKey() {
|
|
82
|
+
const sqliteClause = this.sqliteNull();
|
|
83
|
+
if (sqliteClause) {
|
|
84
|
+
return sqliteClause.instanceKey();
|
|
85
|
+
}
|
|
86
|
+
return `${this.col}${this.op}${rawValue(this.value)}`;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
class isNullClause {
|
|
90
|
+
constructor(col) {
|
|
91
|
+
this.col = col;
|
|
92
|
+
}
|
|
93
|
+
clause(idx) {
|
|
94
|
+
return `${this.col} IS NULL`;
|
|
95
|
+
}
|
|
96
|
+
values() {
|
|
97
|
+
return [];
|
|
98
|
+
}
|
|
99
|
+
logValues() {
|
|
100
|
+
return [];
|
|
101
|
+
}
|
|
102
|
+
instanceKey() {
|
|
103
|
+
return `${this.col} IS NULL`;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
class isNotNullClause {
|
|
107
|
+
constructor(col) {
|
|
108
|
+
this.col = col;
|
|
109
|
+
}
|
|
110
|
+
clause(idx) {
|
|
111
|
+
return `${this.col} IS NOT NULL`;
|
|
112
|
+
}
|
|
113
|
+
values() {
|
|
114
|
+
return [];
|
|
115
|
+
}
|
|
116
|
+
logValues() {
|
|
117
|
+
return [];
|
|
118
|
+
}
|
|
119
|
+
instanceKey() {
|
|
120
|
+
return `${this.col} IS NOT NULL`;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
class arraySimpleClause {
|
|
124
|
+
constructor(col, value, op) {
|
|
125
|
+
this.col = col;
|
|
126
|
+
this.value = value;
|
|
127
|
+
this.op = op;
|
|
128
|
+
}
|
|
129
|
+
clause(idx) {
|
|
130
|
+
if (db_1.default.getDialect() === db_1.Dialect.Postgres) {
|
|
131
|
+
return `$${idx} ${this.op} ANY(${this.col})`;
|
|
132
|
+
}
|
|
133
|
+
return `${this.col} ${this.op} ?`;
|
|
134
|
+
}
|
|
45
135
|
values() {
|
|
46
136
|
if (isSensitive(this.value)) {
|
|
47
137
|
return [this.value.value()];
|
|
@@ -145,12 +235,100 @@ class compositeClause {
|
|
|
145
235
|
return keys.join(this.sep);
|
|
146
236
|
}
|
|
147
237
|
}
|
|
238
|
+
class tsQueryClause {
|
|
239
|
+
constructor(col, val, tsVectorCol) {
|
|
240
|
+
this.col = col;
|
|
241
|
+
this.val = val;
|
|
242
|
+
this.tsVectorCol = tsVectorCol;
|
|
243
|
+
}
|
|
244
|
+
isTsQuery(val) {
|
|
245
|
+
return typeof val !== "string";
|
|
246
|
+
}
|
|
247
|
+
getInfo() {
|
|
248
|
+
if (this.isTsQuery(this.val)) {
|
|
249
|
+
return { value: this.val.value, language: this.val.language };
|
|
250
|
+
}
|
|
251
|
+
return {
|
|
252
|
+
language: "english",
|
|
253
|
+
value: this.val,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
clause(idx) {
|
|
257
|
+
const { language } = this.getInfo();
|
|
258
|
+
if (db_1.Dialect.Postgres === db_1.default.getDialect()) {
|
|
259
|
+
if (this.tsVectorCol) {
|
|
260
|
+
return `to_tsvector(${this.col}) @@ ${this.getFunction()}('${language}', $${idx})`;
|
|
261
|
+
}
|
|
262
|
+
return `${this.col} @@ ${this.getFunction()}('${language}', $${idx})`;
|
|
263
|
+
}
|
|
264
|
+
// FYI this doesn't actually work for sqlite since different
|
|
265
|
+
return `${this.col} @@ ${this.getFunction()}('${language}', ?)`;
|
|
266
|
+
}
|
|
267
|
+
values() {
|
|
268
|
+
const { value } = this.getInfo();
|
|
269
|
+
return [value];
|
|
270
|
+
}
|
|
271
|
+
logValues() {
|
|
272
|
+
const { value } = this.getInfo();
|
|
273
|
+
return [value];
|
|
274
|
+
}
|
|
275
|
+
getFunction() {
|
|
276
|
+
return "to_tsquery";
|
|
277
|
+
}
|
|
278
|
+
instanceKey() {
|
|
279
|
+
const { language, value } = this.getInfo();
|
|
280
|
+
if (this.tsVectorCol) {
|
|
281
|
+
return `to_tsvector(${this.col})@@${this.getFunction()}:${language}:${value}`;
|
|
282
|
+
}
|
|
283
|
+
return `${this.col}@@${this.getFunction()}:${language}:${value}`;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
class plainToTsQueryClause extends tsQueryClause {
|
|
287
|
+
getFunction() {
|
|
288
|
+
return "plainto_tsquery";
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
class phraseToTsQueryClause extends tsQueryClause {
|
|
292
|
+
getFunction() {
|
|
293
|
+
return "phraseto_tsquery";
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
class websearchTosQueryClause extends tsQueryClause {
|
|
297
|
+
getFunction() {
|
|
298
|
+
return "websearch_to_tsquery";
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
// TODO we need to check sqlite version...
|
|
302
|
+
function ArrayEq(col, value) {
|
|
303
|
+
return new arraySimpleClause(col, value, "=");
|
|
304
|
+
}
|
|
305
|
+
exports.ArrayEq = ArrayEq;
|
|
306
|
+
function ArrayNotEq(col, value) {
|
|
307
|
+
return new arraySimpleClause(col, value, "!=");
|
|
308
|
+
}
|
|
309
|
+
exports.ArrayNotEq = ArrayNotEq;
|
|
310
|
+
function ArrayGreater(col, value) {
|
|
311
|
+
return new arraySimpleClause(col, value, ">");
|
|
312
|
+
}
|
|
313
|
+
exports.ArrayGreater = ArrayGreater;
|
|
314
|
+
function ArrayLess(col, value) {
|
|
315
|
+
return new arraySimpleClause(col, value, "<");
|
|
316
|
+
}
|
|
317
|
+
exports.ArrayLess = ArrayLess;
|
|
318
|
+
function ArrayGreaterEq(col, value) {
|
|
319
|
+
return new arraySimpleClause(col, value, ">=");
|
|
320
|
+
}
|
|
321
|
+
exports.ArrayGreaterEq = ArrayGreaterEq;
|
|
322
|
+
function ArrayLessEq(col, value) {
|
|
323
|
+
return new arraySimpleClause(col, value, "<=");
|
|
324
|
+
}
|
|
325
|
+
exports.ArrayLessEq = ArrayLessEq;
|
|
148
326
|
function Eq(col, value) {
|
|
149
|
-
return new simpleClause(col, value, "=");
|
|
327
|
+
return new simpleClause(col, value, "=", new isNullClause(col));
|
|
150
328
|
}
|
|
151
329
|
exports.Eq = Eq;
|
|
152
330
|
function NotEq(col, value) {
|
|
153
|
-
return new simpleClause(col, value, "!=");
|
|
331
|
+
return new simpleClause(col, value, "!=", new isNotNullClause(col));
|
|
154
332
|
}
|
|
155
333
|
exports.NotEq = NotEq;
|
|
156
334
|
function Greater(col, value) {
|
|
@@ -173,6 +351,15 @@ function And(...args) {
|
|
|
173
351
|
return new compositeClause(args, " AND ");
|
|
174
352
|
}
|
|
175
353
|
exports.And = And;
|
|
354
|
+
function AndOptional(...args) {
|
|
355
|
+
// @ts-ignore
|
|
356
|
+
let filtered = args.filter((v) => v !== undefined);
|
|
357
|
+
if (filtered.length === 1) {
|
|
358
|
+
return filtered[0];
|
|
359
|
+
}
|
|
360
|
+
return And(...filtered);
|
|
361
|
+
}
|
|
362
|
+
exports.AndOptional = AndOptional;
|
|
176
363
|
function Or(...args) {
|
|
177
364
|
return new compositeClause(args, " OR ");
|
|
178
365
|
}
|
|
@@ -182,6 +369,60 @@ function In(col, ...values) {
|
|
|
182
369
|
return new inClause(col, values);
|
|
183
370
|
}
|
|
184
371
|
exports.In = In;
|
|
372
|
+
// if string defaults to english
|
|
373
|
+
// https://www.postgresql.org/docs/current/textsearch-controls.html#TEXTSEARCH-PARSING-QUERIES
|
|
374
|
+
// to_tsquery
|
|
375
|
+
// plainto_tsquery
|
|
376
|
+
// phraseto_tsquery;
|
|
377
|
+
// websearch_to_tsquery
|
|
378
|
+
function TsQuery(col, val) {
|
|
379
|
+
return new tsQueryClause(col, val);
|
|
380
|
+
}
|
|
381
|
+
exports.TsQuery = TsQuery;
|
|
382
|
+
function PlainToTsQuery(col, val) {
|
|
383
|
+
return new plainToTsQueryClause(col, val);
|
|
384
|
+
}
|
|
385
|
+
exports.PlainToTsQuery = PlainToTsQuery;
|
|
386
|
+
function PhraseToTsQuery(col, val) {
|
|
387
|
+
return new phraseToTsQueryClause(col, val);
|
|
388
|
+
}
|
|
389
|
+
exports.PhraseToTsQuery = PhraseToTsQuery;
|
|
390
|
+
function WebsearchToTsQuery(col, val) {
|
|
391
|
+
return new websearchTosQueryClause(col, val);
|
|
392
|
+
}
|
|
393
|
+
exports.WebsearchToTsQuery = WebsearchToTsQuery;
|
|
394
|
+
// TsVectorColTsQuery is used when the column is not a tsvector field e.g.
|
|
395
|
+
// when there's an index just on the field and is not a combination of multiple fields
|
|
396
|
+
function TsVectorColTsQuery(col, val) {
|
|
397
|
+
return new tsQueryClause(col, val, true);
|
|
398
|
+
}
|
|
399
|
+
exports.TsVectorColTsQuery = TsVectorColTsQuery;
|
|
400
|
+
// TsVectorPlainToTsQuery is used when the column is not a tsvector field e.g.
|
|
401
|
+
// when there's an index just on the field and is not a combination of multiple fields
|
|
402
|
+
// TODO do these 4 need TsQuery because would be nice to have language?
|
|
403
|
+
// it seems to default to the config of the column
|
|
404
|
+
function TsVectorPlainToTsQuery(col, val) {
|
|
405
|
+
return new plainToTsQueryClause(col, val, true);
|
|
406
|
+
}
|
|
407
|
+
exports.TsVectorPlainToTsQuery = TsVectorPlainToTsQuery;
|
|
408
|
+
// TsVectorPhraseToTsQuery is used when the column is not a tsvector field e.g.
|
|
409
|
+
// when there's an index just on the field and is not a combination of multiple fields
|
|
410
|
+
function TsVectorPhraseToTsQuery(col, val) {
|
|
411
|
+
return new phraseToTsQueryClause(col, val, true);
|
|
412
|
+
}
|
|
413
|
+
exports.TsVectorPhraseToTsQuery = TsVectorPhraseToTsQuery;
|
|
414
|
+
// TsVectorWebsearchToTsQuery is used when the column is not a tsvector field e.g.
|
|
415
|
+
// when there's an index just on the field and is not a combination of multiple fields
|
|
416
|
+
function TsVectorWebsearchToTsQuery(col, val) {
|
|
417
|
+
return new websearchTosQueryClause(col, val, true);
|
|
418
|
+
}
|
|
419
|
+
exports.TsVectorWebsearchToTsQuery = TsVectorWebsearchToTsQuery;
|
|
420
|
+
// TODO would be nice to support this with building blocks but not supporting for now
|
|
421
|
+
// AND: foo & bar,
|
|
422
|
+
// OR: foo | bar
|
|
423
|
+
// followed by: foo <-> bar
|
|
424
|
+
// NOT: !foo
|
|
425
|
+
// starts_with: theo:*
|
|
185
426
|
// wrap a query in the db with this to ensure that it doesn't show up in the logs
|
|
186
427
|
// e.g. if querying for password, SSN, etc
|
|
187
428
|
// we'll pass the right fields to query and log something along the lines of `****`
|