@saltcorn/data 0.6.1 → 0.6.2-beta.0

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.
@@ -16,7 +16,7 @@ const { traverseSync } = require("../../models/layout");
16
16
  const { structuredClone } = require("../../utils");
17
17
  const db = require("../../db");
18
18
 
19
- /**
19
+ /**
20
20
  * @function
21
21
  * @param {string} viewname
22
22
  * @param {Table|object} table
@@ -42,8 +42,8 @@ const action_url = contract(
42
42
  );
43
43
 
44
44
  /**
45
- * @param {string} url
46
- * @param {object} req
45
+ * @param {string} url
46
+ * @param {object} req
47
47
  * @param {object} opts
48
48
  * @param {string} opts.action_name
49
49
  * @param {string} opts.action_label
@@ -55,7 +55,7 @@ const action_url = contract(
55
55
  * @param {string} opts.action_bgcol
56
56
  * @param {string} opts.action_bordercol
57
57
  * @param {string} opts.action_textcol
58
- * @param {*} __
58
+ * @param {*} __
59
59
  * @returns {object}
60
60
  */
61
61
  const action_link = (
@@ -174,7 +174,7 @@ const make_link = contract(
174
174
  );
175
175
 
176
176
  /**
177
- * @param {string} s
177
+ * @param {string} s
178
178
  * @returns {object}
179
179
  */
180
180
  const parse_view_select = (s) => {
@@ -197,7 +197,7 @@ const parse_view_select = (s) => {
197
197
  };
198
198
 
199
199
  //todo: use above to simplify code
200
- /**
200
+ /**
201
201
  * @function
202
202
  * @param {object} opts
203
203
  * @param {string} opts.view,
@@ -338,7 +338,7 @@ const view_linker = contract(
338
338
  );
339
339
 
340
340
  /**
341
- * @param {string} nm
341
+ * @param {string} nm
342
342
  * @returns {boolean}
343
343
  */
344
344
  const action_requires_write = (nm) => {
@@ -347,7 +347,7 @@ const action_requires_write = (nm) => {
347
347
  if (nm.startsWith("Toggle")) return true;
348
348
  };
349
349
 
350
- /**
350
+ /**
351
351
  * @function
352
352
  * @param {string} viewname
353
353
  * @param {Table|object} table
@@ -527,8 +527,8 @@ const get_viewable_fields = contract(
527
527
  );
528
528
 
529
529
  /**
530
- * @param {string} fname
531
- * @param {object} req
530
+ * @param {string} fname
531
+ * @param {object} req
532
532
  * @returns {string}
533
533
  */
534
534
  const sortlinkForName = (fname, req) => {
@@ -543,10 +543,10 @@ const sortlinkForName = (fname, req) => {
543
543
  };
544
544
 
545
545
  /**
546
- * @param {object} column
547
- * @param {object} f
548
- * @param {object} req
549
- * @param {*} __
546
+ * @param {object} column
547
+ * @param {object} f
548
+ * @param {object} req
549
+ * @param {*} __
550
550
  * @returns {string}
551
551
  */
552
552
  const headerLabelForName = (column, f, req, __) => {
@@ -563,7 +563,7 @@ const headerLabelForName = (column, f, req, __) => {
563
563
  return label + arrow;
564
564
  };
565
565
 
566
- /**
566
+ /**
567
567
  * @function
568
568
  * @param {Field[]} fields
569
569
  * @param {object} state
@@ -597,12 +597,12 @@ const splitUniques = contract(
597
597
  );
598
598
 
599
599
  /**
600
- * @param {object} table
601
- * @param {string} viewname
602
- * @param {object[]} [columns]
603
- * @param {object} layout0
604
- * @param {boolean} id
605
- * @param {object} req
600
+ * @param {object} table
601
+ * @param {string} viewname
602
+ * @param {object[]} [columns]
603
+ * @param {object} layout0
604
+ * @param {boolean} id
605
+ * @param {object} req
606
606
  * @returns {Promise<Form>}
607
607
  */
608
608
  const getForm = async (table, viewname, columns, layout0, id, req) => {
@@ -626,7 +626,7 @@ const getForm = async (table, viewname, columns, layout0, id, req) => {
626
626
  }
627
627
  if (f.calculated)
628
628
  f.sourceURL = `/field/show-calculated/${table.name}/${f.name}/${f.fieldview}`;
629
-
629
+ f.attributes = { ...column.configuration, ...f.attributes };
630
630
  return f;
631
631
  } else if (table.name === "users" && column.field_name === "password") {
632
632
  return new Field({
@@ -669,15 +669,14 @@ const getForm = async (table, viewname, columns, layout0, id, req) => {
669
669
  fields: tfields,
670
670
  layout,
671
671
  });
672
- await form.fill_fkey_options();
673
672
  if (id) form.hidden("id");
674
673
  return form;
675
674
  };
676
675
 
677
676
  /**
678
- * @param {object} table
679
- * @param {object} req
680
- * @param {object} fixed
677
+ * @param {object} table
678
+ * @param {object} req
679
+ * @param {object} fixed
681
680
  * @returns {Promise<object>}
682
681
  */
683
682
  const fill_presets = async (table, req, fixed) => {
package/db/index.js CHANGED
@@ -24,9 +24,12 @@ var connectObj = getConnectObject();
24
24
  /** @type {db/sqlite|db/pg|null} */
25
25
  let dbmodule = null;
26
26
  try {
27
- dbmodule = is_sqlite(connectObj) ?
28
- require("@saltcorn/sqlite/sqlite")(getConnectObject) :
29
- require("@saltcorn/postgres/postgres")(getConnectObject);
27
+ if(is_sqlite(connectObj)) {
28
+ dbmodule = require("@saltcorn/sqlite/sqlite");
29
+ dbmodule.init(getConnectObject);
30
+ }
31
+ else
32
+ dbmodule = require("@saltcorn/postgres/postgres")(getConnectObject);
30
33
  } catch(e) {
31
34
  console.log("No database package found.")
32
35
  throw e;
@@ -9,7 +9,7 @@ const estraverse = require("estraverse");
9
9
  const astring = require("astring");
10
10
 
11
11
  /**
12
- * @param {string} s
12
+ * @param {string} s
13
13
  * @returns {boolean|void}
14
14
  */
15
15
  function expressionValidator(s) {
@@ -23,7 +23,7 @@ function expressionValidator(s) {
23
23
  }
24
24
 
25
25
  /**
26
- * @param {string} expression
26
+ * @param {string} expression
27
27
  * @returns {string}
28
28
  */
29
29
  function jsexprToSQL(expression) {
@@ -32,11 +32,11 @@ function jsexprToSQL(expression) {
32
32
  }
33
33
 
34
34
  /**
35
- * @param {string} expression
35
+ * @param {string} expression
36
36
  * @throws {Error}
37
37
  * @returns {object}
38
38
  */
39
- function jsexprToWhere(expression) {
39
+ function jsexprToWhere(expression, extraCtx = {}) {
40
40
  if (!expression) return {};
41
41
  try {
42
42
  const ast = acorn.parseExpressionAt(expression, 0, {
@@ -47,18 +47,26 @@ function jsexprToWhere(expression) {
47
47
  const compile = (node) =>
48
48
  ({
49
49
  BinaryExpression() {
50
+ const cleft = compile(node.left);
51
+ const cright = compile(node.right);
52
+ const cmp =
53
+ typeof cleft === "string" || cleft === null
54
+ ? { eq: [cleft, cright] }
55
+ : typeof cleft === "symbol"
56
+ ? { [cleft.description]: cright }
57
+ : { [cleft]: cright };
50
58
  return {
51
- "=="({ left, right }) {
52
- return { [compile(left)]: compile(right) };
59
+ "=="() {
60
+ return cmp;
53
61
  },
54
- "==="({ left, right }) {
55
- return { [compile(left)]: compile(right) };
62
+ "==="() {
63
+ return cmp;
56
64
  },
57
- "!="({ left, right }) {
58
- return { not: { [compile(left)]: compile(right) } };
65
+ "!="() {
66
+ return { not: cmp };
59
67
  },
60
- "!=="({ left, right }) {
61
- return { not: { [compile(left)]: compile(right) } };
68
+ "!=="() {
69
+ return { not: cmp };
62
70
  },
63
71
  }[node.operator](node);
64
72
  },
@@ -83,14 +91,18 @@ function jsexprToWhere(expression) {
83
91
  }[node.operator](node);
84
92
  },
85
93
  Identifier({ name }) {
86
- return name;
94
+ if (name[0] === "$") {
95
+ return extraCtx[name.substring(1)] || null;
96
+ }
97
+ return Symbol(name);
87
98
  },
88
99
  Literal({ value }) {
89
100
  return value;
90
101
  },
91
102
  }[node.type](node));
92
103
  return compile(ast);
93
- } catch {
104
+ } catch (e) {
105
+ console.error(e);
94
106
  throw new Error(
95
107
  `Expression "${expression}" is too complicated, I do not understand`
96
108
  );
@@ -98,8 +110,8 @@ function jsexprToWhere(expression) {
98
110
  }
99
111
 
100
112
  /**
101
- * @param {string} expression
102
- * @param {object[]} statefuns
113
+ * @param {string} expression
114
+ * @param {object[]} statefuns
103
115
  * @returns {object}
104
116
  */
105
117
  function transform_for_async(expression, statefuns) {
@@ -125,8 +137,8 @@ function transform_for_async(expression, statefuns) {
125
137
  }
126
138
 
127
139
  /**
128
- * @param {string} expression
129
- * @param {object[]} fields
140
+ * @param {string} expression
141
+ * @param {object[]} fields
130
142
  * @returns {any}
131
143
  */
132
144
  function get_expression_function(expression, fields) {
@@ -142,8 +154,8 @@ function get_expression_function(expression, fields) {
142
154
  }
143
155
 
144
156
  /**
145
- * @param {string} expression
146
- * @param {object[]} fields
157
+ * @param {string} expression
158
+ * @param {object[]} fields
147
159
  * @param {object} [extraContext = {}]
148
160
  * @returns {any}
149
161
  */
@@ -162,9 +174,9 @@ function get_async_expression_function(expression, fields, extraContext = {}) {
162
174
  }
163
175
 
164
176
  /**
165
- * @param {object[]} rows
166
- * @param {object[]} fields
167
- * @returns {object[]}
177
+ * @param {object[]} rows
178
+ * @param {object[]} fields
179
+ * @returns {object[]}
168
180
  */
169
181
  function apply_calculated_fields(rows, fields) {
170
182
  let hasExprs = false;
@@ -196,8 +208,8 @@ function apply_calculated_fields(rows, fields) {
196
208
  }
197
209
 
198
210
  /**
199
- * @param {*} row
200
- * @param {*} fields
211
+ * @param {*} row
212
+ * @param {*} fields
201
213
  * @returns {Promise<any>}
202
214
  */
203
215
  const apply_calculated_fields_stored = async (row, fields) => {
package/models/field.js CHANGED
@@ -7,7 +7,7 @@
7
7
 
8
8
  const db = require("../db");
9
9
  const { contract, is } = require("contractis");
10
- const { recalculate_for_stored } = require("./expression");
10
+ const { recalculate_for_stored, jsexprToWhere } = require("./expression");
11
11
  const { sqlsanitize } = require("@saltcorn/db-common/internal.js");
12
12
  const { InvalidAdminAction } = require("../utils");
13
13
  const { mkWhere } = require("../db");
@@ -153,7 +153,13 @@ class Field {
153
153
  * @param {object} where
154
154
  * @returns {Promise<void>}
155
155
  */
156
- async fill_fkey_options(force_allow_none = false, where) {
156
+ async fill_fkey_options(force_allow_none = false, where0, extraCtx) {
157
+ const where =
158
+ where0 ||
159
+ (this.attributes.where
160
+ ? jsexprToWhere(this.attributes.where, extraCtx)
161
+ : undefined);
162
+ //console.log(where);
157
163
  if (
158
164
  this.is_fkey &&
159
165
  (this.type !== "File" ||
@@ -288,7 +294,7 @@ class Field {
288
294
  }
289
295
 
290
296
  /**
291
- * @param {object} whole_rec
297
+ * @param {object} whole_rec
292
298
  * @returns {object}
293
299
  */
294
300
  validate(whole_rec) {
@@ -316,8 +322,8 @@ class Field {
316
322
  }
317
323
 
318
324
  /**
319
- *
320
- * @param {object} where
325
+ *
326
+ * @param {object} where
321
327
  * @param {object} [selectopts]
322
328
  * @returns {Field[]}
323
329
  */
@@ -327,7 +333,7 @@ class Field {
327
333
  }
328
334
 
329
335
  /**
330
- * @param {object} where
336
+ * @param {object} where
331
337
  * @returns {Promise<Field>}
332
338
  */
333
339
  static async findOne(where) {
@@ -352,8 +358,8 @@ class Field {
352
358
  }
353
359
 
354
360
  /**
355
- *
356
- * @param {boolean} not_null
361
+ *
362
+ * @param {boolean} not_null
357
363
  * @returns {Promise<void>}
358
364
  */
359
365
  async toggle_not_null(not_null) {
@@ -370,7 +376,7 @@ class Field {
370
376
  }
371
377
 
372
378
  /**
373
- * @param {object} new_field
379
+ * @param {object} new_field
374
380
  * @returns {Promise<void>}
375
381
  */
376
382
  async alter_sql_type(new_field) {
@@ -421,7 +427,7 @@ class Field {
421
427
  }
422
428
 
423
429
  /**
424
- * @param {object} v
430
+ * @param {object} v
425
431
  * @returns {Promise<void>}
426
432
  */
427
433
  async update(v) {
@@ -523,7 +529,7 @@ class Field {
523
529
  }
524
530
 
525
531
  /**
526
- * @param {object} table
532
+ * @param {object} table
527
533
  * @returns {Promise<void>}
528
534
  */
529
535
  async enable_fkey_constraint(table) {
@@ -542,7 +548,7 @@ class Field {
542
548
  }
543
549
 
544
550
  /**
545
- * @param {object} fld
551
+ * @param {object} fld
546
552
  * @param {boolean} [bare = false]
547
553
  * @returns {Promise<Field>}
548
554
  */
package/models/form.js CHANGED
@@ -14,9 +14,9 @@ const Field = require("./field");
14
14
  */
15
15
  class Form {
16
16
  /**
17
- * Constructor
18
- * @param o
19
- */
17
+ * Constructor
18
+ * @param o
19
+ */
20
20
  constructor(o) {
21
21
  this.fields = o.fields.map((f) =>
22
22
  f.constructor.name === Object.name ? new Field(f) : f
@@ -48,7 +48,7 @@ class Form {
48
48
  }
49
49
 
50
50
  /**
51
- * @param {object} ks
51
+ * @param {object} ks
52
52
  */
53
53
  hidden(...ks) {
54
54
  ks.forEach((k) => {
@@ -66,8 +66,9 @@ class Form {
66
66
  * @param {boolean} [force_allow_none = false]
67
67
  */
68
68
  async fill_fkey_options(force_allow_none = false) {
69
+ //console.log(this.values);
69
70
  for (const f of this.fields) {
70
- await f.fill_fkey_options(force_allow_none);
71
+ await f.fill_fkey_options(force_allow_none, undefined, this.values);
71
72
  }
72
73
  }
73
74
 
@@ -98,7 +99,7 @@ class Form {
98
99
  }
99
100
 
100
101
  /**
101
- * @param {*} v
102
+ * @param {*} v
102
103
  * @returns {object}
103
104
  */
104
105
  validate(v) {
package/package.json CHANGED
@@ -1,21 +1,23 @@
1
1
  {
2
2
  "name": "@saltcorn/data",
3
- "version": "0.6.1",
3
+ "version": "0.6.2-beta.0",
4
4
  "description": "Data models for Saltcorn, open-source no-code platform",
5
5
  "homepage": "https://saltcorn.com",
6
6
  "scripts": {
7
- "test": "jest --runInBand"
7
+ "test": "jest --runInBand",
8
+ "tsc": "echo \"Error: no TypeScript support yet\"",
9
+ "clean": "echo \"Error: no TypeScript support yet\""
8
10
  },
9
11
  "author": "Tom Nielsen",
10
12
  "license": "MIT",
11
13
  "main": "index.js",
12
14
  "optionalDependencies": {
13
- "@saltcorn/postgres": "0.6.1",
14
- "@saltcorn/sqlite": "0.6.1"
15
+ "@saltcorn/postgres": "0.6.2-beta.0",
16
+ "@saltcorn/sqlite": "0.6.2-beta.0"
15
17
  },
16
18
  "dependencies": {
17
- "@saltcorn/markup": "0.6.1",
18
- "@saltcorn/db-common": "0.6.1",
19
+ "@saltcorn/markup": "0.6.2-beta.0",
20
+ "@saltcorn/db-common": "0.6.2-beta.0",
19
21
  "acorn": "^8.0.3",
20
22
  "adm-zip": "0.5.5",
21
23
  "astring": "^1.4.3",
@@ -47,7 +49,10 @@
47
49
  "jest": "^25.1.0"
48
50
  },
49
51
  "jest": {
50
- "testEnvironment": "node"
52
+ "testEnvironment": "node",
53
+ "moduleNameMapper": {
54
+ "@saltcorn/sqlite/(.*)": "@saltcorn/sqlite/dist/$1"
55
+ }
51
56
  },
52
57
  "publishConfig": {
53
58
  "access": "public"
package/plugin-helper.js CHANGED
@@ -112,16 +112,19 @@ const stateToQueryString = contract(
112
112
  */
113
113
  const calcfldViewOptions = contract(
114
114
  is.fun(
115
- [is.array(is.class("Field")), is.bool],
115
+ [is.array(is.class("Field")), is.str],
116
116
  is.obj({ field_view_options: is.objVals(is.array(is.str)) })
117
117
  ),
118
- (fields, isEdit) => {
119
- var fvs = {};
118
+ (fields, mode) => {
119
+ const isEdit = mode === "edit";
120
+ const isFilter = mode === "filter";
121
+ let fvs = {};
120
122
  const handlesTextStyle = {};
121
123
  fields.forEach((f) => {
122
124
  handlesTextStyle[f.name] = [];
123
125
  if (f.type === "File") {
124
- if (!isEdit) fvs[f.name] = Object.keys(getState().fileviews);
126
+ if (!isEdit && !isFilter)
127
+ fvs[f.name] = Object.keys(getState().fileviews);
125
128
  else fvs[f.name] = ["upload"];
126
129
  } else if (f.type === "Key") {
127
130
  if (isEdit) fvs[f.name] = Object.keys(getState().keyFieldviews);
@@ -129,7 +132,7 @@ const calcfldViewOptions = contract(
129
132
  if (f.reftable && f.reftable.fields) {
130
133
  const { field_view_options } = calcfldViewOptions(
131
134
  f.reftable.fields,
132
- isEdit
135
+ mode
133
136
  );
134
137
  for (const jf of f.reftable.fields) {
135
138
  fvs[`${f.name}.${jf.name}`] = field_view_options[jf.name];
@@ -142,15 +145,20 @@ const calcfldViewOptions = contract(
142
145
  });
143
146
  } else if (f.type && f.type.fieldviews) {
144
147
  const tfvs = Object.entries(f.type.fieldviews).filter(([k, fv]) =>
145
- f.calculated ? !fv.isEdit : !fv.isEdit || isEdit
148
+ f.calculated ? !fv.isEdit : !fv.isEdit || isEdit || isFilter
146
149
  );
147
150
  let tfvs_ordered = [];
148
151
  if (isEdit) {
149
152
  tfvs_ordered = [
150
153
  ...tfvs.filter(([k, fv]) => fv.isEdit),
151
- ...tfvs.filter(([k, fv]) => !fv.isEdit),
154
+ ...tfvs.filter(([k, fv]) => !fv.isEdit && !fv.isFilter),
152
155
  ];
153
- } else tfvs_ordered = tfvs;
156
+ } else if (isFilter) {
157
+ tfvs_ordered = [
158
+ ...tfvs.filter(([k, fv]) => fv.isFilter),
159
+ ...tfvs.filter(([k, fv]) => fv.isEdit),
160
+ ];
161
+ } else tfvs_ordered = tfvs.filter(([k, fv]) => !fv.isFilter);
154
162
  fvs[f.name] = tfvs_ordered.map(([k, fv]) => {
155
163
  if (fv && fv.handlesTextStyle) handlesTextStyle[f.name].push(k);
156
164
  return k;
@@ -323,7 +331,7 @@ const field_picker_fields = contract(
323
331
  }
324
332
  }
325
333
  const fldOptions = fields.map((f) => f.name);
326
- const { field_view_options } = calcfldViewOptions(fields, false);
334
+ const { field_view_options } = calcfldViewOptions(fields, "show");
327
335
  const fieldViewConfigForms = await calcfldViewConfig(fields, false);
328
336
  const fvConfigFields = [];
329
337
  for (const [field_name, fvOptFields] of Object.entries(
@@ -954,6 +962,14 @@ const stateFieldsToWhere = contract(
954
962
  const dfield = fields.find((fld) => fld.name == datefield);
955
963
  if (dfield)
956
964
  addOrCreateList(qstate, datefield, { lt: new Date(v), equal: true });
965
+ } else if (k.startsWith("_gte_")) {
966
+ const datefield = db.sqlsanitize(k.replace("_gte_", ""));
967
+ const dfield = fields.find((fld) => fld.name == datefield);
968
+ if (dfield) addOrCreateList(qstate, datefield, { gt: v, equal: true });
969
+ } else if (k.startsWith("_lte_")) {
970
+ const datefield = db.sqlsanitize(k.replace("_lte_", ""));
971
+ const dfield = fields.find((fld) => fld.name == datefield);
972
+ if (dfield) addOrCreateList(qstate, datefield, { lt: v, equal: true });
957
973
  } else if (
958
974
  field &&
959
975
  field.type.name === "String" &&
@@ -964,7 +980,9 @@ const stateFieldsToWhere = contract(
964
980
  } else if (field && field.type.name === "Bool" && state[k] === "?") {
965
981
  // omit
966
982
  } else if (field && field.type && field.type.read)
967
- qstate[k] = field.type.read(v);
983
+ qstate[k] = Array.isArray(v)
984
+ ? { or: v.map(field.type.read) }
985
+ : field.type.read(v);
968
986
  else if (field) qstate[k] = v;
969
987
  else if (k.includes(".")) {
970
988
  const kpath = k.split(".");
@@ -1124,7 +1142,9 @@ const readState = (state, fields, req) => {
1124
1142
  fields.forEach((f) => {
1125
1143
  const current = state[f.name];
1126
1144
  if (typeof current !== "undefined") {
1127
- if (f.type.read) state[f.name] = f.type.read(current);
1145
+ if (Array.isArray(current) && f.type.read) {
1146
+ state[f.name] = current.map(f.type.read);
1147
+ } else if (f.type.read) state[f.name] = f.type.read(current);
1128
1148
  else if (typeof current === "string" && current.startsWith("Preset:")) {
1129
1149
  const preset = f.presets[current.replace("Preset:", "")];
1130
1150
  state[f.name] = preset(req);
@@ -7,6 +7,7 @@ const {
7
7
  get_expression_function,
8
8
  transform_for_async,
9
9
  expressionValidator,
10
+ jsexprToWhere,
10
11
  } = require("../models/expression");
11
12
 
12
13
  getState().registerPlugin("base", require("../base-plugin"));
@@ -219,3 +220,24 @@ describe("expressions", () => {
219
220
  expect(expressionValidator("2+")).toBe("Unexpected end of input");
220
221
  });
221
222
  });
223
+ describe("jsexprToWhere", () => {
224
+ it("translates equality", () => {
225
+ expect(jsexprToWhere("foo==4")).toEqual({ foo: 4 });
226
+ });
227
+ it("translates equal to col", () => {
228
+ expect(jsexprToWhere("foo==bar").foo.description).toBe("bar");
229
+ });
230
+ it("translates context", () => {
231
+ expect(jsexprToWhere("foo==$bar", { bar: 5 })).toEqual({ foo: 5 });
232
+ });
233
+ it("translates context", () => {
234
+ const w = jsexprToWhere("$father !== null && married_to === $father", {
235
+ father: "1",
236
+ });
237
+ expect(w).toEqual({ married_to: "1", not: { eq: ["1", null] } });
238
+ });
239
+ it("translates context", () => {
240
+ const w = jsexprToWhere("$father !== null && married_to === $father", {});
241
+ expect(w).toEqual({ married_to: null, not: { eq: [null, null] } });
242
+ });
243
+ });