@saltcorn/data 0.6.1-beta.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.
package/models/field.js CHANGED
@@ -7,8 +7,8 @@
7
7
 
8
8
  const db = require("../db");
9
9
  const { contract, is } = require("contractis");
10
- const { recalculate_for_stored } = require("./expression");
11
- const { sqlsanitize } = require("../db/internal.js");
10
+ const { recalculate_for_stored, jsexprToWhere } = require("./expression");
11
+ const { sqlsanitize } = require("@saltcorn/db-common/internal.js");
12
12
  const { InvalidAdminAction } = require("../utils");
13
13
  const { mkWhere } = require("../db");
14
14
 
@@ -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/file.js CHANGED
@@ -50,7 +50,14 @@ class File {
50
50
  * @param selectopts
51
51
  * @returns {Promise<*>}
52
52
  */
53
- static async find(where, selectopts) {
53
+ static async find(where, selectopts = {}) {
54
+ if (selectopts.cached) {
55
+ const { getState } = require("../db/state");
56
+ const files = Object.values(getState().files).sort((a, b) =>
57
+ a.filename > b.filename ? 1 : -1
58
+ );
59
+ return files.map((t) => new File(t));
60
+ }
54
61
  const db_flds = await db.select("_sc_files", where, selectopts);
55
62
  return db_flds.map((dbf) => new File(dbf));
56
63
  }
@@ -157,7 +164,7 @@ class File {
157
164
  return { error: e.message };
158
165
  }
159
166
  }
160
-
167
+
161
168
  /**
162
169
  * MIME type of the file
163
170
  * @type {string}
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/models/page.js CHANGED
@@ -32,7 +32,7 @@ const {
32
32
  */
33
33
  class Page {
34
34
  /**
35
- * @param {object} o
35
+ * @param {object} o
36
36
  */
37
37
  constructor(o) {
38
38
  this.name = o.name;
@@ -56,6 +56,10 @@ class Page {
56
56
  * @returns {Promise<*>}
57
57
  */
58
58
  static async find(where, selectopts = { orderBy: "name", nocase: true }) {
59
+ if (selectopts.cached) {
60
+ const { getState } = require("../db/state");
61
+ return getState().pages.map((t) => new Page(t));
62
+ }
59
63
  const db_flds = await db.select("_sc_pages", where, selectopts);
60
64
  return db_flds.map((dbf) => new Page(dbf));
61
65
  }
package/models/table.js CHANGED
@@ -5,7 +5,11 @@
5
5
  * @subcategory models
6
6
  */
7
7
  const db = require("../db");
8
- const { sqlsanitize, mkWhere, mkSelectOptions } = require("../db/internal.js");
8
+ const {
9
+ sqlsanitize,
10
+ mkWhere,
11
+ mkSelectOptions,
12
+ } = require("@saltcorn/db-common/internal.js");
9
13
  const Field = require("./field");
10
14
  const Trigger = require("./trigger");
11
15
  const {
@@ -87,7 +91,7 @@ const normalise_error_message = (msg) =>
87
91
  class Table {
88
92
  /**
89
93
  * Table constructor
90
- * @param {object} o
94
+ * @param {object} o
91
95
  */
92
96
  constructor(o) {
93
97
  this.name = o.name;
@@ -141,6 +145,10 @@ class Table {
141
145
  * @returns {Promise<Table[]>} table list
142
146
  */
143
147
  static async find(where, selectopts = { orderBy: "name", nocase: true }) {
148
+ if (selectopts.cached) {
149
+ const { getState } = require("../db/state");
150
+ return getState().tables.map((t) => new Table(t));
151
+ }
144
152
  const tbls = await db.select("_sc_tables", where, selectopts);
145
153
 
146
154
  return tbls.map((t) => new Table(t));
package/models/tenant.js CHANGED
@@ -7,8 +7,9 @@
7
7
  const db = require("../db");
8
8
  const reset = require("../db/reset_schema");
9
9
  const { contract, is } = require("contractis");
10
- const { sqlsanitize } = require("../db/internal");
10
+ const { sqlsanitize } = require("@saltcorn/db-common/internal");
11
11
  const { setConfig } = require("./config");
12
+ const fs = require("fs").promises;
12
13
 
13
14
  /**
14
15
  * List all Tenants
@@ -42,10 +43,13 @@ const getAllTenants = contract(
42
43
  * @returns {Promise<void>}
43
44
  */
44
45
  const createTenant = contract(
45
- is.fun([is.str, is.maybe(is.str), is.maybe(is.str), is.maybe(is.str)], is.promise(is.undefined)),
46
+ is.fun(
47
+ [is.str, is.maybe(is.str), is.maybe(is.str), is.maybe(is.str)],
48
+ is.promise(is.undefined)
49
+ ),
46
50
  // TODO how to set names for arguments
47
- async ( subdomain, newurl, email, description) => {
48
- // normalize domain name
51
+ async (subdomain, newurl, email, description) => {
52
+ // normalize domain name
49
53
  const saneDomain = domain_sanitize(subdomain);
50
54
 
51
55
  // add email
@@ -70,11 +74,30 @@ const createTenant = contract(
70
74
  });
71
75
  }
72
76
  );
77
+ const copy_tenant_template = async ({
78
+ tenant_template,
79
+ target,
80
+ loadAndSaveNewPlugin,
81
+ }) => {
82
+ const { create_backup, restore } = require("./backup");
83
+ // TODO use a hygenic name for backup file
84
+ const backupFile = await db.runWithTenant(tenant_template, create_backup);
85
+ await db.runWithTenant(target, async () => {
86
+ await restore(backupFile, loadAndSaveNewPlugin, true);
87
+
88
+ await db.updateWhere("_sc_files", { user_id: null }, {});
89
+ await db.deleteWhere("users", {});
90
+ await db.reset_sequence("users");
91
+ //
92
+ });
93
+ await fs.unlink(backupFile);
94
+ };
95
+
73
96
  /**
74
97
  * Delete Tenant
75
98
  * Note! This is deleting all tenant data in database!
76
99
  * @function
77
- * @param {string} sub
100
+ * @param {string} sub
78
101
  * @returns {Promise<void>}
79
102
  */
80
103
  const deleteTenant = contract(
@@ -117,4 +140,5 @@ module.exports = {
117
140
  domain_sanitize,
118
141
  deleteTenant,
119
142
  eachTenant,
143
+ copy_tenant_template,
120
144
  };
package/models/view.js CHANGED
@@ -28,7 +28,7 @@ const { renderForm } = require("@saltcorn/markup");
28
28
  class View {
29
29
  /**
30
30
  * View constructor
31
- * @param {object} o
31
+ * @param {object} o
32
32
  */
33
33
  constructor(o) {
34
34
  this.name = o.name;
@@ -55,7 +55,7 @@ class View {
55
55
  }
56
56
 
57
57
  /**
58
- * @param {object} where
58
+ * @param {object} where
59
59
  * @returns {View}
60
60
  */
61
61
  static findOne(where) {
@@ -78,6 +78,10 @@ class View {
78
78
  * @returns {Promise<View[]>}
79
79
  */
80
80
  static async find(where, selectopts = { orderBy: "name", nocase: true }) {
81
+ if (selectopts.cached) {
82
+ const { getState } = require("../db/state");
83
+ return getState().views.map((t) => new View(t));
84
+ }
81
85
  const views = await db.select("_sc_views", where, selectopts);
82
86
 
83
87
  return views.map((v) => new View(v));
@@ -159,7 +163,7 @@ class View {
159
163
  }
160
164
 
161
165
  /**
162
- * @param {function} pred
166
+ * @param {function} pred
163
167
  * @returns {Promise<object>}
164
168
  */
165
169
  static async find_all_views_where(pred) {
@@ -182,7 +186,7 @@ class View {
182
186
  }
183
187
 
184
188
  /**
185
- * @param {Table|object} table
189
+ * @param {Table|object} table
186
190
  * @returns {Promise<View[]>}
187
191
  */
188
192
  static async find_possible_links_to_table(table) {
@@ -275,7 +279,7 @@ class View {
275
279
  }
276
280
 
277
281
  /**
278
- * @param {*} arg
282
+ * @param {*} arg
279
283
  * @returns {Promise<object>}
280
284
  */
281
285
  async authorise_post(arg) {
@@ -284,7 +288,7 @@ class View {
284
288
  }
285
289
 
286
290
  /**
287
- * @param {*} arg
291
+ * @param {*} arg
288
292
  * @returns {Promise<object>}
289
293
  */
290
294
  async authorise_get(arg) {
@@ -334,9 +338,9 @@ class View {
334
338
  }
335
339
 
336
340
  /**
337
- * @param {*} query
338
- * @param {*} req
339
- * @param {*} res
341
+ * @param {*} query
342
+ * @param {*} req
343
+ * @param {*} res
340
344
  * @returns {Promise<object>}
341
345
  */
342
346
  async run_possibly_on_page(query, req, res) {
@@ -361,8 +365,8 @@ class View {
361
365
  }
362
366
 
363
367
  /**
364
- * @param {*} query
365
- * @param {*} extraArgs
368
+ * @param {*} query
369
+ * @param {*} extraArgs
366
370
  * @throws {InvalidConfiguration}
367
371
  * @returns {Promise<object>}
368
372
  */
@@ -405,9 +409,9 @@ class View {
405
409
  }
406
410
 
407
411
  /**
408
- * @param {*} query
409
- * @param {*} body
410
- * @param {*} extraArgs
412
+ * @param {*} query
413
+ * @param {*} body
414
+ * @param {*} extraArgs
411
415
  * @returns {Promise<object>}
412
416
  */
413
417
  async runPost(query, body, extraArgs) {
@@ -423,10 +427,10 @@ class View {
423
427
  }
424
428
 
425
429
  /**
426
- * @param {*} route
427
- * @param {*} body
428
- * @param {*} res
429
- * @param {*} extraArgs
430
+ * @param {*} route
431
+ * @param {*} body
432
+ * @param {*} res
433
+ * @param {*} extraArgs
430
434
  * @returns {Promise<void>}
431
435
  */
432
436
  async runRoute(route, body, res, extraArgs) {
@@ -444,7 +448,7 @@ class View {
444
448
  }
445
449
 
446
450
  /**
447
- * @param {object} req_query
451
+ * @param {object} req_query
448
452
  * @returns {object}
449
453
  */
450
454
  combine_state_and_default_state(req_query) {
@@ -463,8 +467,8 @@ class View {
463
467
  }
464
468
 
465
469
  /**
466
- * @param {object} query
467
- * @param {object} req
470
+ * @param {object} query
471
+ * @param {object} req
468
472
  * @returns {Promise<Form|null>}
469
473
  */
470
474
  async get_state_form(query, req) {
@@ -501,7 +505,7 @@ class View {
501
505
  }
502
506
 
503
507
  /**
504
- * @param {object} req
508
+ * @param {object} req
505
509
  * @returns {Promise<object>}
506
510
  */
507
511
  async get_config_flow(req) {
package/package.json CHANGED
@@ -1,16 +1,23 @@
1
1
  {
2
2
  "name": "@saltcorn/data",
3
- "version": "0.6.1-beta.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",
14
+ "optionalDependencies": {
15
+ "@saltcorn/postgres": "0.6.2-beta.0",
16
+ "@saltcorn/sqlite": "0.6.2-beta.0"
17
+ },
12
18
  "dependencies": {
13
- "@saltcorn/markup": "0.6.1-beta.1",
19
+ "@saltcorn/markup": "0.6.2-beta.0",
20
+ "@saltcorn/db-common": "0.6.2-beta.0",
14
21
  "acorn": "^8.0.3",
15
22
  "adm-zip": "0.5.5",
16
23
  "astring": "^1.4.3",
@@ -32,10 +39,7 @@
32
39
  "moment-timezone": "^0.5.33",
33
40
  "node-fetch": "2.6.2",
34
41
  "nodemailer": "^6.4.17",
35
- "pg": "^8.2.1",
36
- "pg-copy-streams": "^5.1.1",
37
42
  "pluralize": "^8.0.0",
38
- "sqlite3": "^5.0.2",
39
43
  "tmp-promise": "^3.0.2",
40
44
  "uuid": "^8.2.0"
41
45
  },
@@ -45,7 +49,10 @@
45
49
  "jest": "^25.1.0"
46
50
  },
47
51
  "jest": {
48
- "testEnvironment": "node"
52
+ "testEnvironment": "node",
53
+ "moduleNameMapper": {
54
+ "@saltcorn/sqlite/(.*)": "@saltcorn/sqlite/dist/$1"
55
+ }
49
56
  },
50
57
  "publishConfig": {
51
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
+ });