@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.
@@ -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/db.test.js CHANGED
@@ -1,162 +1,8 @@
1
- const { sqlsanitize, mkWhere, sqlsanitizeAllowDots } = require("./internal");
2
1
  const db = require("./index.js");
3
2
  const Table = require("../models/table");
4
3
 
5
4
  afterAll(db.close);
6
5
 
7
- describe("sqlsanitize", () => {
8
- it("should not alter valid name", () => {
9
- expect(sqlsanitize("ffoo_oo")).toBe("ffoo_oo");
10
- });
11
- it("should remove spaces", () => {
12
- expect(sqlsanitize(" ")).toBe("");
13
- });
14
- it("should remove chars from invalid name", () => {
15
- expect(sqlsanitize("ffoo--oo--uu")).toBe("ffoooouu");
16
- });
17
- it("should not allow dots", () => {
18
- expect(sqlsanitize("ffoo.oo")).toBe("ffoooo");
19
- });
20
- it("should allow dots when specified", () => {
21
- expect(sqlsanitizeAllowDots("ffoo.oo")).toBe("ffoo.oo");
22
- });
23
- it("should allow quotes when dots specified", () => {
24
- expect(sqlsanitizeAllowDots('ffoo."oo"')).toBe('ffoo."oo"');
25
- });
26
- it("should allow numbers", () => {
27
- expect(sqlsanitize("ff1oo_oo")).toBe("ff1oo_oo");
28
- });
29
- it("should not allow numbers in initial position", () => {
30
- expect(sqlsanitize("1ffoo_o1o")).toBe("_1ffoo_o1o");
31
- });
32
- });
33
-
34
- describe("mkWhere", () => {
35
- it("should empty on no arg", () => {
36
- expect(mkWhere()).toStrictEqual({ values: [], where: "" });
37
- });
38
- it("should empty on null obj arg", () => {
39
- expect(mkWhere({})).toStrictEqual({ values: [], where: "" });
40
- });
41
- it("should query json", () => {
42
- expect(mkWhere({ foo: { json: ["bar", 5] } })).toStrictEqual({
43
- values: [5],
44
- where: `where "foo"->>'bar'=$1`,
45
- });
46
- });
47
-
48
- it("should set id", () => {
49
- expect(mkWhere({ id: 5 })).toStrictEqual({
50
- values: [5],
51
- where: 'where "id"=$1',
52
- });
53
- expect(mkWhere({ id: 5, hello: "world" })).toStrictEqual({
54
- values: [5, "world"],
55
- where: 'where "id"=$1 and "hello"=$2',
56
- });
57
- });
58
- it("should query null", () => {
59
- expect(mkWhere({ id: null })).toStrictEqual({
60
- values: [],
61
- where: 'where "id" is null',
62
- });
63
- expect(mkWhere({ id: null, foo: 1 })).toStrictEqual({
64
- values: [1],
65
- where: 'where "id" is null and "foo"=$1',
66
- });
67
- expect(mkWhere({ foo: 1, id: null })).toStrictEqual({
68
- values: [1],
69
- where: 'where "foo"=$1 and "id" is null',
70
- });
71
- });
72
- it("should query lt/gt", () => {
73
- expect(mkWhere({ id: { lt: 5 } })).toStrictEqual({
74
- values: [5],
75
- where: 'where "id"<$1',
76
- });
77
- expect(mkWhere({ id: { gt: 8 } })).toStrictEqual({
78
- values: [8],
79
- where: 'where "id">$1',
80
- });
81
- expect(mkWhere({ id: { lt: 5, equal: true } })).toStrictEqual({
82
- values: [5],
83
- where: 'where "id"<=$1',
84
- });
85
- expect(mkWhere({ id: { gt: 8, equal: true } })).toStrictEqual({
86
- values: [8],
87
- where: 'where "id">=$1',
88
- });
89
- expect(mkWhere({ id: [{ gt: 0 }, { lt: 10 }] })).toStrictEqual({
90
- values: [0, 10],
91
- where: 'where "id">$1 and "id"<$2',
92
- });
93
- expect(mkWhere({ id: { or: [{ gt: 10 }, { lt: 5 }] } })).toStrictEqual({
94
- values: [10, 5],
95
- where: 'where ("id">$1 or "id"<$2)',
96
- });
97
- });
98
- it("should query subselect", () => {
99
- expect(
100
- mkWhere({
101
- id: [{ inSelect: { table: "foo", field: "bar", where: { baz: 7 } } }],
102
- })
103
- ).toStrictEqual({
104
- values: [7],
105
- where: 'where "id" in (select bar from foo where "baz"=$1)',
106
- });
107
- expect(
108
- mkWhere({
109
- age: 45,
110
- id: [{ inSelect: { table: "foo", field: "bar", where: { baz: 7 } } }],
111
- name: "Alice",
112
- })
113
- ).toStrictEqual({
114
- values: [45, 7, "Alice"],
115
- where: `where "age"=$1 and "id" in (select bar from foo where "baz"=$2) and "name"=$3`,
116
- });
117
- });
118
- it("should query or", () => {
119
- expect(mkWhere({ or: [{ id: 5 }, { x: 7 }] })).toStrictEqual({
120
- values: [5, 7],
121
- where: 'where ("id"=$1 or "x"=$2)',
122
- });
123
- expect(mkWhere({ or: [{ id: 5 }, { x: { gt: 7 } }] })).toStrictEqual({
124
- values: [5, 7],
125
- where: 'where ("id"=$1 or "x">$2)',
126
- });
127
- expect(mkWhere({ or: [{ id: 5 }, { x: 7, y: 8 }] })).toStrictEqual({
128
- values: [5, 7, 8],
129
- where: 'where ("id"=$1 or "x"=$2 and "y"=$3)',
130
- });
131
- expect(mkWhere({ not: { id: 5 } })).toStrictEqual({
132
- values: [5],
133
- where: 'where not ("id"=$1)',
134
- });
135
- expect(mkWhere({ not: { id: 5, y: 1 } })).toStrictEqual({
136
- values: [5, 1],
137
- where: 'where not ("id"=$1 and "y"=$2)',
138
- });
139
- expect(mkWhere({ not: { y: { in: [1, 2, 3] } } })).toStrictEqual({
140
- values: [[1, 2, 3]],
141
- where: 'where not ("y" = ANY ($1))',
142
- });
143
- expect(mkWhere({ y: { in: [1, 2, 3] } })).toStrictEqual({
144
- values: [[1, 2, 3]],
145
- where: 'where "y" = ANY ($1)',
146
- });
147
- /*
148
- TODO
149
- expect(mkWhere([{ id: 5 }, { x: 7 }])).toStrictEqual({
150
- values: [5, 7],
151
- where: 'where "id"=$1 and "x"=$2',
152
- });
153
- expect(mkWhere([{ or: [{ id: 5 }, { x: 7 }] }, { z: 9 }])).toStrictEqual({
154
- values: [5, 7, 9],
155
- where: 'where ("id"=$1 or "x"=$2) and "z"=$3',
156
- });*/
157
- });
158
- });
159
-
160
6
  describe("where", () => {
161
7
  it("should support in", async () => {
162
8
  await Table.create("myothertable");
package/db/index.js CHANGED
@@ -10,28 +10,33 @@
10
10
  * @namespace db_overview
11
11
  * @property {module:db/connect} connect
12
12
  * @property {module:db/fixtures} fixtures
13
- * @property {module:db/internal} internal
14
- * @property {module:db/multi-tenant} multi-tenant
15
- * @property {module:db/pg} pg
16
13
  * @property {module:db/reset_schema} reset_schema
17
- * @property {module:db/single-tenant} single-tenant
18
- * @property {module:db/sqlite} sqlite
19
14
  * @property {module:db/state} state
20
15
  * @property {module:db/connect} connect
21
- * @property {module:db/tenants} tenants
22
16
  *
23
17
  * @category saltcorn-data
24
18
  * @subcategory db
25
19
  */
26
20
  const { getConnectObject, is_sqlite } = require("./connect");
27
- const { sqlsanitize, mkWhere } = require("./internal");
21
+ const { sqlsanitize, mkWhere } = require("@saltcorn/db-common/internal");
28
22
  var connectObj = getConnectObject();
29
23
 
30
- /** @type {db/sqlite|db/pg} */
31
- const dbmodule = is_sqlite(connectObj) ? require("./sqlite") : require("./pg");
24
+ /** @type {db/sqlite|db/pg|null} */
25
+ let dbmodule = null;
26
+ try {
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);
33
+ } catch(e) {
34
+ console.log("No database package found.")
35
+ throw e;
36
+ }
32
37
 
33
38
  /** @type {db/tenant} */
34
- const tenant = require("./tenants");
39
+ const tenant = require("@saltcorn/db-common/tenants")(getConnectObject());
35
40
 
36
41
  /** @type {boolean} */
37
42
  const isSQLite = is_sqlite(connectObj);
package/db/state.js CHANGED
@@ -21,7 +21,11 @@ const { migrate } = require("../migrate");
21
21
  const File = require("../models/file");
22
22
  const Trigger = require("../models/trigger");
23
23
  const View = require("../models/view");
24
- const { getAllTenants, createTenant } = require("../models/tenant");
24
+ const {
25
+ getAllTenants,
26
+ createTenant,
27
+ copy_tenant_template,
28
+ } = require("../models/tenant");
25
29
  const {
26
30
  getAllConfigOrDefaults,
27
31
  setConfig,
@@ -35,7 +39,7 @@ const path = require("path");
35
39
  const fs = require("fs");
36
40
 
37
41
  /**
38
- * @param {object} v
42
+ * @param {object} v
39
43
  * @returns {void}
40
44
  */
41
45
  const process_send = (v) => {
@@ -43,14 +47,14 @@ const process_send = (v) => {
43
47
  };
44
48
 
45
49
  /**
46
- * State Class
47
- * @category saltcorn-data
48
- */
50
+ * State Class
51
+ * @category saltcorn-data
52
+ */
49
53
  class State {
50
54
  /**
51
- * State constructor
52
- * @param {string} tenant description
53
- */
55
+ * State constructor
56
+ * @param {string} tenant description
57
+ */
54
58
  constructor(tenant) {
55
59
  this.tenant = tenant;
56
60
  this.views = [];
@@ -483,16 +487,16 @@ class State {
483
487
  }
484
488
 
485
489
  /**
486
- *
487
- * @param {function} f
490
+ *
491
+ * @param {function} f
488
492
  */
489
493
  setRoomEmitter(f) {
490
494
  this.roomEmitter = f;
491
495
  }
492
496
 
493
497
  /**
494
- *
495
- * @param {*} args
498
+ *
499
+ * @param {*} args
496
500
  */
497
501
  emitRoom(...args) {
498
502
  if (this.roomEmitter) this.roomEmitter(...args);
@@ -610,11 +614,29 @@ const init_multi_tenant = async (plugin_loader, disableMigrate) => {
610
614
  * @param {boolean} noSignalOrDB
611
615
  * @returns {Promise<void>}
612
616
  */
613
- const create_tenant = async (t, plugin_loader, newurl, noSignalOrDB) => {
617
+ const create_tenant = async (
618
+ t,
619
+ plugin_loader,
620
+ newurl,
621
+ noSignalOrDB,
622
+ loadAndSaveNewPlugin
623
+ ) => {
614
624
  if (!noSignalOrDB) await createTenant(t, newurl);
615
625
  tenants[t] = new State(t);
616
626
  await db.runWithTenant(t, plugin_loader);
617
- if (!noSignalOrDB) process_send({ createTenant: t });
627
+ if (!noSignalOrDB) {
628
+ const tenant_template = singleton.getConfig("tenant_template");
629
+ if (tenant_template) {
630
+ //create backup
631
+ await copy_tenant_template({
632
+ tenant_template,
633
+ target: t,
634
+ state: tenants[t],
635
+ loadAndSaveNewPlugin,
636
+ });
637
+ }
638
+ process_send({ createTenant: t });
639
+ }
618
640
  };
619
641
  /**
620
642
  * Restart tenant
package/models/config.js CHANGED
@@ -16,7 +16,7 @@ const defaultTimezone = moment.tz.guess();
16
16
 
17
17
  /**
18
18
  * Config variables types
19
- * @namespace
19
+ * @namespace
20
20
  * @category saltcorn-data
21
21
  */
22
22
  const configTypes = {
@@ -237,6 +237,12 @@ const configTypes = {
237
237
  "Show a warning to users creating a tenant disclaiming warrenty of availability or security",
238
238
  },
239
239
  /** @type {object} */
240
+ tenant_template: {
241
+ type: "Tenant",
242
+ label: "New tenant template",
243
+ blurb: "Copy site structure for new tenants from this tenant",
244
+ },
245
+ /** @type {object} */
240
246
  development_mode: {
241
247
  type: "Bool",
242
248
  label: "Development mode",
@@ -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) => {