@saltcorn/data 1.6.0-alpha.9 → 1.6.0-beta.10

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.
Files changed (298) hide show
  1. package/dist/base-plugin/actions.d.ts +194 -65
  2. package/dist/base-plugin/actions.d.ts.map +1 -1
  3. package/dist/base-plugin/actions.js +220 -80
  4. package/dist/base-plugin/actions.js.map +1 -1
  5. package/dist/base-plugin/fieldviews.d.ts +1 -0
  6. package/dist/base-plugin/fieldviews.d.ts.map +1 -1
  7. package/dist/base-plugin/fieldviews.js +5 -2
  8. package/dist/base-plugin/fieldviews.js.map +1 -1
  9. package/dist/base-plugin/fileviews.d.ts +6 -0
  10. package/dist/base-plugin/fileviews.d.ts.map +1 -1
  11. package/dist/base-plugin/fileviews.js +14 -0
  12. package/dist/base-plugin/fileviews.js.map +1 -1
  13. package/dist/base-plugin/index.d.ts +220 -70
  14. package/dist/base-plugin/index.d.ts.map +1 -1
  15. package/dist/base-plugin/types.d.ts +26 -11
  16. package/dist/base-plugin/types.d.ts.map +1 -1
  17. package/dist/base-plugin/types.js +47 -13
  18. package/dist/base-plugin/types.js.map +1 -1
  19. package/dist/base-plugin/viewtemplates/edit.d.ts.map +1 -1
  20. package/dist/base-plugin/viewtemplates/edit.js +15 -11
  21. package/dist/base-plugin/viewtemplates/edit.js.map +1 -1
  22. package/dist/base-plugin/viewtemplates/feed.d.ts.map +1 -1
  23. package/dist/base-plugin/viewtemplates/feed.js +1 -1
  24. package/dist/base-plugin/viewtemplates/feed.js.map +1 -1
  25. package/dist/base-plugin/viewtemplates/filter.d.ts.map +1 -1
  26. package/dist/base-plugin/viewtemplates/filter.js +13 -5
  27. package/dist/base-plugin/viewtemplates/filter.js.map +1 -1
  28. package/dist/base-plugin/viewtemplates/list.d.ts +2 -1
  29. package/dist/base-plugin/viewtemplates/list.d.ts.map +1 -1
  30. package/dist/base-plugin/viewtemplates/list.js +20 -2
  31. package/dist/base-plugin/viewtemplates/list.js.map +1 -1
  32. package/dist/base-plugin/viewtemplates/room.d.ts.map +1 -1
  33. package/dist/base-plugin/viewtemplates/room.js +1 -1
  34. package/dist/base-plugin/viewtemplates/room.js.map +1 -1
  35. package/dist/base-plugin/viewtemplates/show.d.ts.map +1 -1
  36. package/dist/base-plugin/viewtemplates/show.js +3 -0
  37. package/dist/base-plugin/viewtemplates/show.js.map +1 -1
  38. package/dist/db/fixtures.d.ts.map +1 -1
  39. package/dist/db/fixtures.js +31 -1
  40. package/dist/db/fixtures.js.map +1 -1
  41. package/dist/db/state.d.ts +9 -3
  42. package/dist/db/state.d.ts.map +1 -1
  43. package/dist/db/state.js +63 -16
  44. package/dist/db/state.js.map +1 -1
  45. package/dist/migrate.d.ts.map +1 -1
  46. package/dist/migrate.js +12 -5
  47. package/dist/migrate.js.map +1 -1
  48. package/dist/migrations/202603101553.d.ts +4 -0
  49. package/dist/migrations/202603101553.d.ts.map +1 -0
  50. package/dist/migrations/202603101553.js +22 -0
  51. package/dist/migrations/202603101553.js.map +1 -0
  52. package/dist/migrations/202604091531.d.ts +2 -0
  53. package/dist/migrations/202604091531.d.ts.map +1 -0
  54. package/dist/migrations/202604091531.js +9 -0
  55. package/dist/migrations/202604091531.js.map +1 -0
  56. package/dist/migrations/202604111200.d.ts +2 -0
  57. package/dist/migrations/202604111200.d.ts.map +1 -0
  58. package/dist/migrations/202604111200.js +15 -0
  59. package/dist/migrations/202604111200.js.map +1 -0
  60. package/dist/migrations/202604141200.d.ts +2 -0
  61. package/dist/migrations/202604141200.d.ts.map +1 -0
  62. package/dist/migrations/202604141200.js +14 -0
  63. package/dist/migrations/202604141200.js.map +1 -0
  64. package/dist/mobile-mocks/node/assert.d.ts +1 -0
  65. package/dist/mobile-mocks/node/assert.d.ts.map +1 -0
  66. package/dist/mobile-mocks/node/assert.js +2 -0
  67. package/dist/mobile-mocks/node/assert.js.map +1 -0
  68. package/dist/mobile-mocks/node/fs/promises.d.ts +1 -0
  69. package/dist/mobile-mocks/node/fs/promises.d.ts.map +1 -1
  70. package/dist/mobile-mocks/node/fs/promises.js +4 -0
  71. package/dist/mobile-mocks/node/fs/promises.js.map +1 -1
  72. package/dist/mobile-mocks/node/fs.d.ts +2 -0
  73. package/dist/mobile-mocks/node/fs.d.ts.map +1 -1
  74. package/dist/mobile-mocks/node/fs.js +36 -0
  75. package/dist/mobile-mocks/node/fs.js.map +1 -1
  76. package/dist/mobile-mocks/npm/npm-registry-fetch.d.ts +3 -0
  77. package/dist/mobile-mocks/npm/npm-registry-fetch.d.ts.map +1 -0
  78. package/dist/mobile-mocks/npm/npm-registry-fetch.js +3 -0
  79. package/dist/mobile-mocks/npm/npm-registry-fetch.js.map +1 -0
  80. package/dist/mobile-mocks/saltcorn/admin-models-tenant.d.ts +1 -0
  81. package/dist/mobile-mocks/saltcorn/admin-models-tenant.d.ts.map +1 -0
  82. package/dist/mobile-mocks/saltcorn/admin-models-tenant.js +2 -0
  83. package/dist/mobile-mocks/saltcorn/admin-models-tenant.js.map +1 -0
  84. package/dist/mobile-mocks/saltcorn/plugins-loader-plugin-installer.d.ts +1 -0
  85. package/dist/mobile-mocks/saltcorn/plugins-loader-plugin-installer.d.ts.map +1 -0
  86. package/dist/mobile-mocks/saltcorn/plugins-loader-plugin-installer.js +2 -0
  87. package/dist/mobile-mocks/saltcorn/plugins-loader-plugin-installer.js.map +1 -0
  88. package/dist/mobile-mocks/saltcorn/plugins-loader-stable-versioning.d.ts +1 -0
  89. package/dist/mobile-mocks/saltcorn/plugins-loader-stable-versioning.d.ts.map +1 -0
  90. package/dist/mobile-mocks/saltcorn/plugins-loader-stable-versioning.js +2 -0
  91. package/dist/mobile-mocks/saltcorn/plugins-loader-stable-versioning.js.map +1 -0
  92. package/dist/models/config.d.ts.map +1 -1
  93. package/dist/models/config.js +46 -1
  94. package/dist/models/config.js.map +1 -1
  95. package/dist/models/discovery.d.ts +14 -0
  96. package/dist/models/discovery.d.ts.map +1 -1
  97. package/dist/models/discovery.js +63 -0
  98. package/dist/models/discovery.js.map +1 -1
  99. package/dist/models/expression.d.ts +8 -5
  100. package/dist/models/expression.d.ts.map +1 -1
  101. package/dist/models/expression.js +77 -28
  102. package/dist/models/expression.js.map +1 -1
  103. package/dist/models/field.d.ts.map +1 -1
  104. package/dist/models/field.js +106 -19
  105. package/dist/models/field.js.map +1 -1
  106. package/dist/models/index.d.ts +13 -3
  107. package/dist/models/index.d.ts.map +1 -1
  108. package/dist/models/index.js +1 -1
  109. package/dist/models/index.js.map +1 -1
  110. package/dist/models/internal/push_message_helper.d.ts.map +1 -1
  111. package/dist/models/internal/push_message_helper.js +5 -2
  112. package/dist/models/internal/push_message_helper.js.map +1 -1
  113. package/dist/models/metadata.d.ts +2 -1
  114. package/dist/models/metadata.d.ts.map +1 -1
  115. package/dist/models/metadata.js +5 -0
  116. package/dist/models/metadata.js.map +1 -1
  117. package/dist/models/page.d.ts +1 -0
  118. package/dist/models/page.d.ts.map +1 -1
  119. package/dist/models/page.js +37 -19
  120. package/dist/models/page.js.map +1 -1
  121. package/dist/models/plugin.d.ts +59 -2
  122. package/dist/models/plugin.d.ts.map +1 -1
  123. package/dist/models/plugin.js +324 -3
  124. package/dist/models/plugin.js.map +1 -1
  125. package/dist/models/scheduler.d.ts.map +1 -1
  126. package/dist/models/scheduler.js +15 -5
  127. package/dist/models/scheduler.js.map +1 -1
  128. package/dist/models/table.d.ts +11 -1
  129. package/dist/models/table.d.ts.map +1 -1
  130. package/dist/models/table.js +344 -127
  131. package/dist/models/table.js.map +1 -1
  132. package/dist/models/trigger.d.ts +3 -1
  133. package/dist/models/trigger.d.ts.map +1 -1
  134. package/dist/models/trigger.js +38 -13
  135. package/dist/models/trigger.js.map +1 -1
  136. package/dist/models/user.d.ts.map +1 -1
  137. package/dist/models/user.js +5 -0
  138. package/dist/models/user.js.map +1 -1
  139. package/dist/models/view.d.ts +1 -0
  140. package/dist/models/view.d.ts.map +1 -1
  141. package/dist/models/view.js +74 -14
  142. package/dist/models/view.js.map +1 -1
  143. package/dist/models/workflow.d.ts.map +1 -1
  144. package/dist/models/workflow.js +6 -0
  145. package/dist/models/workflow.js.map +1 -1
  146. package/dist/models/workflow_run.d.ts.map +1 -1
  147. package/dist/models/workflow_run.js +3 -2
  148. package/dist/models/workflow_run.js.map +1 -1
  149. package/dist/models/workflow_step.d.ts +1 -1
  150. package/dist/models/workflow_step.d.ts.map +1 -1
  151. package/dist/models/workflow_step.js +2 -1
  152. package/dist/models/workflow_step.js.map +1 -1
  153. package/dist/plugin-helper.d.ts +3 -11
  154. package/dist/plugin-helper.d.ts.map +1 -1
  155. package/dist/plugin-helper.js +106 -60
  156. package/dist/plugin-helper.js.map +1 -1
  157. package/dist/standard-menu.d.ts.map +1 -1
  158. package/dist/standard-menu.js +19 -0
  159. package/dist/standard-menu.js.map +1 -1
  160. package/dist/tests/remote_query_helper.js +1 -1
  161. package/dist/tests/remote_query_helper.js.map +1 -1
  162. package/dist/utils.d.ts +15 -0
  163. package/dist/utils.d.ts.map +1 -1
  164. package/dist/utils.js +102 -5
  165. package/dist/utils.js.map +1 -1
  166. package/dist/viewable_fields.d.ts +3 -3
  167. package/dist/viewable_fields.d.ts.map +1 -1
  168. package/dist/viewable_fields.js +60 -16
  169. package/dist/viewable_fields.js.map +1 -1
  170. package/dist/web-mobile-commons.d.ts.map +1 -1
  171. package/dist/web-mobile-commons.js +2 -1
  172. package/dist/web-mobile-commons.js.map +1 -1
  173. package/package.json +12 -9
  174. package/webpack.config.js +12 -0
  175. package/dist/tests/actions.test.d.ts +0 -2
  176. package/dist/tests/actions.test.d.ts.map +0 -1
  177. package/dist/tests/actions.test.js +0 -936
  178. package/dist/tests/actions.test.js.map +0 -1
  179. package/dist/tests/auth.test.d.ts +0 -2
  180. package/dist/tests/auth.test.d.ts.map +0 -1
  181. package/dist/tests/auth.test.js +0 -824
  182. package/dist/tests/auth.test.js.map +0 -1
  183. package/dist/tests/auxtest.test.d.ts +0 -2
  184. package/dist/tests/auxtest.test.d.ts.map +0 -1
  185. package/dist/tests/auxtest.test.js +0 -562
  186. package/dist/tests/auxtest.test.js.map +0 -1
  187. package/dist/tests/base.test.d.ts +0 -2
  188. package/dist/tests/base.test.d.ts.map +0 -1
  189. package/dist/tests/base.test.js +0 -30
  190. package/dist/tests/base.test.js.map +0 -1
  191. package/dist/tests/calc.test.d.ts +0 -2
  192. package/dist/tests/calc.test.d.ts.map +0 -1
  193. package/dist/tests/calc.test.js +0 -1081
  194. package/dist/tests/calc.test.js.map +0 -1
  195. package/dist/tests/composite_pk.test.d.ts +0 -2
  196. package/dist/tests/composite_pk.test.d.ts.map +0 -1
  197. package/dist/tests/composite_pk.test.js +0 -98
  198. package/dist/tests/composite_pk.test.js.map +0 -1
  199. package/dist/tests/config.test.d.ts +0 -2
  200. package/dist/tests/config.test.d.ts.map +0 -1
  201. package/dist/tests/config.test.js +0 -86
  202. package/dist/tests/config.test.js.map +0 -1
  203. package/dist/tests/db.test.d.ts +0 -2
  204. package/dist/tests/db.test.d.ts.map +0 -1
  205. package/dist/tests/db.test.js +0 -178
  206. package/dist/tests/db.test.js.map +0 -1
  207. package/dist/tests/discover.test.d.ts +0 -2
  208. package/dist/tests/discover.test.d.ts.map +0 -1
  209. package/dist/tests/discover.test.js +0 -245
  210. package/dist/tests/discover.test.js.map +0 -1
  211. package/dist/tests/edit.test.d.ts +0 -2
  212. package/dist/tests/edit.test.d.ts.map +0 -1
  213. package/dist/tests/edit.test.js +0 -1161
  214. package/dist/tests/edit.test.js.map +0 -1
  215. package/dist/tests/email.test.d.ts +0 -2
  216. package/dist/tests/email.test.d.ts.map +0 -1
  217. package/dist/tests/email.test.js +0 -255
  218. package/dist/tests/email.test.js.map +0 -1
  219. package/dist/tests/exact_views.test.d.ts +0 -2
  220. package/dist/tests/exact_views.test.d.ts.map +0 -1
  221. package/dist/tests/exact_views.test.js +0 -1363
  222. package/dist/tests/exact_views.test.js.map +0 -1
  223. package/dist/tests/field.test.d.ts +0 -2
  224. package/dist/tests/field.test.d.ts.map +0 -1
  225. package/dist/tests/field.test.js +0 -588
  226. package/dist/tests/field.test.js.map +0 -1
  227. package/dist/tests/fieldviews.test.d.ts +0 -2
  228. package/dist/tests/fieldviews.test.d.ts.map +0 -1
  229. package/dist/tests/fieldviews.test.js +0 -74
  230. package/dist/tests/fieldviews.test.js.map +0 -1
  231. package/dist/tests/file.test.d.ts +0 -2
  232. package/dist/tests/file.test.d.ts.map +0 -1
  233. package/dist/tests/file.test.js +0 -148
  234. package/dist/tests/file.test.js.map +0 -1
  235. package/dist/tests/filter.test.d.ts +0 -2
  236. package/dist/tests/filter.test.d.ts.map +0 -1
  237. package/dist/tests/filter.test.js +0 -496
  238. package/dist/tests/filter.test.js.map +0 -1
  239. package/dist/tests/form.test.d.ts +0 -2
  240. package/dist/tests/form.test.d.ts.map +0 -1
  241. package/dist/tests/form.test.js +0 -264
  242. package/dist/tests/form.test.js.map +0 -1
  243. package/dist/tests/list.test.d.ts +0 -2
  244. package/dist/tests/list.test.d.ts.map +0 -1
  245. package/dist/tests/list.test.js +0 -1037
  246. package/dist/tests/list.test.js.map +0 -1
  247. package/dist/tests/models.test.d.ts +0 -2
  248. package/dist/tests/models.test.d.ts.map +0 -1
  249. package/dist/tests/models.test.js +0 -417
  250. package/dist/tests/models.test.js.map +0 -1
  251. package/dist/tests/page.test.d.ts +0 -2
  252. package/dist/tests/page.test.d.ts.map +0 -1
  253. package/dist/tests/page.test.js +0 -26
  254. package/dist/tests/page.test.js.map +0 -1
  255. package/dist/tests/page_group.test.d.ts +0 -2
  256. package/dist/tests/page_group.test.d.ts.map +0 -1
  257. package/dist/tests/page_group.test.js +0 -51
  258. package/dist/tests/page_group.test.js.map +0 -1
  259. package/dist/tests/plugin.test.d.ts +0 -2
  260. package/dist/tests/plugin.test.d.ts.map +0 -1
  261. package/dist/tests/plugin.test.js +0 -60
  262. package/dist/tests/plugin.test.js.map +0 -1
  263. package/dist/tests/show.test.d.ts +0 -2
  264. package/dist/tests/show.test.d.ts.map +0 -1
  265. package/dist/tests/show.test.js +0 -561
  266. package/dist/tests/show.test.js.map +0 -1
  267. package/dist/tests/state.test.d.ts +0 -2
  268. package/dist/tests/state.test.d.ts.map +0 -1
  269. package/dist/tests/state.test.js +0 -82
  270. package/dist/tests/state.test.js.map +0 -1
  271. package/dist/tests/table.test.d.ts +0 -2
  272. package/dist/tests/table.test.d.ts.map +0 -1
  273. package/dist/tests/table.test.js +0 -2717
  274. package/dist/tests/table.test.js.map +0 -1
  275. package/dist/tests/table_history.test.d.ts +0 -2
  276. package/dist/tests/table_history.test.d.ts.map +0 -1
  277. package/dist/tests/table_history.test.js +0 -413
  278. package/dist/tests/table_history.test.js.map +0 -1
  279. package/dist/tests/tag.test.d.ts +0 -2
  280. package/dist/tests/tag.test.d.ts.map +0 -1
  281. package/dist/tests/tag.test.js +0 -97
  282. package/dist/tests/tag.test.js.map +0 -1
  283. package/dist/tests/user.test.d.ts +0 -2
  284. package/dist/tests/user.test.d.ts.map +0 -1
  285. package/dist/tests/user.test.js +0 -441
  286. package/dist/tests/user.test.js.map +0 -1
  287. package/dist/tests/view.test.d.ts +0 -2
  288. package/dist/tests/view.test.d.ts.map +0 -1
  289. package/dist/tests/view.test.js +0 -699
  290. package/dist/tests/view.test.js.map +0 -1
  291. package/dist/tests/workflow.test.d.ts +0 -2
  292. package/dist/tests/workflow.test.d.ts.map +0 -1
  293. package/dist/tests/workflow.test.js +0 -303
  294. package/dist/tests/workflow.test.js.map +0 -1
  295. package/dist/tests/workflow_run.test.d.ts +0 -2
  296. package/dist/tests/workflow_run.test.d.ts.map +0 -1
  297. package/dist/tests/workflow_run.test.js +0 -922
  298. package/dist/tests/workflow_run.test.js.map +0 -1
@@ -47,7 +47,7 @@ const field_1 = __importDefault(require("./field"));
47
47
  const common_types_1 = require("@saltcorn/types/common_types");
48
48
  const trigger_1 = __importDefault(require("./trigger"));
49
49
  const expression_1 = __importDefault(require("./expression"));
50
- const { apply_calculated_fields, apply_calculated_fields_stored, recalculate_for_stored, get_expression_function, eval_expression, freeVariables, add_free_variables_to_joinfields, removeComments, jsexprToWhere, } = expression_1.default;
50
+ const { apply_calculated_fields, apply_calculated_fields_stored, recalculate_for_stored, get_expression_function, get_async_expression_function, eval_expression, freeVariables, add_free_variables_to_joinfields, removeComments, jsexprToWhere, } = expression_1.default;
51
51
  const uuid_1 = require("uuid");
52
52
  const csvtojson_1 = __importDefault(require("csvtojson"));
53
53
  const moment_1 = __importDefault(require("moment"));
@@ -201,6 +201,17 @@ class Table {
201
201
  this.provider_cfg = stringToJSON(o.provider_cfg);
202
202
  this.provider_name = o.provider_name;
203
203
  this.fields = o.fields.map((f) => new field_1.default(f));
204
+ this.updated_at = ["string", "number"].includes(typeof o.updated_at)
205
+ ? new Date(o.updated_at)
206
+ : o.updated_at;
207
+ }
208
+ static subClass({ user, read_only, } = {}) {
209
+ var _a;
210
+ return _a = class extends this {
211
+ },
212
+ _a.fixed_user = user || undefined,
213
+ _a.read_only = !!read_only,
214
+ _a;
204
215
  }
205
216
  get to_json() {
206
217
  return {
@@ -234,6 +245,7 @@ class Table {
234
245
  const { fields, constraints, ...updDB } = upd_rec;
235
246
  if (updDB.ownership_field_id === "")
236
247
  delete updDB.ownership_field_id;
248
+ updDB.updated_at = new Date();
237
249
  await db_1.default.update("_sc_tables", updDB, tbl.id);
238
250
  //limited refresh if we do not have a client
239
251
  if (!db_1.default.getRequestContext()?.client)
@@ -247,6 +259,7 @@ class Table {
247
259
  if (!db_1.default.getRequestContext()?.client)
248
260
  await Table.state_refresh(true);
249
261
  };
262
+ //console.log({tbl});
250
263
  return t;
251
264
  }
252
265
  /**
@@ -263,9 +276,9 @@ class Table {
263
276
  return where;
264
277
  // todo add string & number as possible types for where
265
278
  if (typeof where === "string")
266
- return Table.findOne({ name: where });
279
+ return this.findOne({ name: where });
267
280
  if (typeof where === "number")
268
- return Table.findOne({ id: where });
281
+ return this.findOne({ id: where });
269
282
  if (typeof where === "undefined")
270
283
  return null;
271
284
  if (where === null)
@@ -283,10 +296,10 @@ class Table {
283
296
  ? (v) => v.name === where.name
284
297
  : satisfies(where));
285
298
  if (tbl?.provider_name) {
286
- return new Table(structuredClone(tbl)).to_provided_table();
299
+ return new this(structuredClone(tbl)).to_provided_table();
287
300
  }
288
301
  else
289
- return tbl ? new Table(structuredClone(tbl)) : null;
302
+ return tbl ? new this(structuredClone(tbl)) : null;
290
303
  }
291
304
  /**
292
305
  * Find Tables
@@ -298,7 +311,7 @@ class Table {
298
311
  if (selectopts.cached) {
299
312
  const { getState } = require("../db/state");
300
313
  return getState()
301
- .tables.map((t) => new Table(structuredClone(t)))
314
+ .tables.map((t) => new this(structuredClone(t)))
302
315
  .filter(satisfies(where || {}));
303
316
  }
304
317
  if (where?.name) {
@@ -316,7 +329,7 @@ class Table {
316
329
  const { getState } = require("../db/state");
317
330
  const provider = getState().table_providers[t.provider_name];
318
331
  if (provider)
319
- t.fields = await applyAsync(provider.fields, t.provider_cfg);
332
+ t.fields = await applyAsync(provider.fields, stringToJSON(t.provider_cfg));
320
333
  else
321
334
  t.fields = [];
322
335
  }
@@ -327,7 +340,7 @@ class Table {
327
340
  t.constraints = constraints
328
341
  .filter((f) => f.table_id === t.id)
329
342
  .map((f) => new _TableConstraint(f));
330
- const tbl = new Table(t);
343
+ const tbl = new this(t);
331
344
  return tbl.to_provided_table();
332
345
  });
333
346
  }
@@ -355,7 +368,7 @@ class Table {
355
368
  t.fields = flds
356
369
  .filter((f) => f.table_id === t.id)
357
370
  .map((f) => new field_1.default(f));
358
- return new Table(t);
371
+ return new this(t);
359
372
  });
360
373
  }
361
374
  return [...dbs, ...externals];
@@ -401,18 +414,19 @@ class Table {
401
414
  * @returns {boolean}
402
415
  */
403
416
  is_owner(user, row) {
404
- if (!user)
417
+ let use_user = this.constructor.fixed_user || user;
418
+ if (!use_user)
405
419
  return false;
406
420
  if (this.ownership_formula && this.fields) {
407
421
  const f = get_expression_function(this.ownership_formula, this.fields);
408
- return !!f(row, user);
422
+ return !!f(row, use_user);
409
423
  }
410
424
  const field_name = this.owner_fieldname();
411
425
  // users are owners of their own row in users table
412
426
  if (this.name === "users" && !field_name)
413
- return !!user.id && `${row?.id}` === `${user.id}`;
427
+ return !!use_user.id && `${row?.id}` === `${use_user.id}`;
414
428
  return (typeof field_name === "string" &&
415
- (row[field_name] === user.id || row[field_name]?.id === user.id));
429
+ (row[field_name] === use_user.id || row[field_name]?.id === use_user.id));
416
430
  }
417
431
  /**
418
432
  * get Ownership options
@@ -553,6 +567,8 @@ class Table {
553
567
  if (pk_type !== "Integer") {
554
568
  const { getState } = require("../db/state");
555
569
  const type = getState().types[pk_type];
570
+ if (!type)
571
+ throw new Error(`Cannot find primary key type ${pk_type} in fields ${JSON.stringify(fields)}`);
556
572
  pk_sql_type = type.sql_name;
557
573
  if (type.primaryKey?.default_sql)
558
574
  pk_sql_type = `${type.sql_name} default ${type.primaryKey?.default_sql}`;
@@ -568,7 +584,11 @@ class Table {
568
584
  */
569
585
  static async create(name, options = {}, //TODO not selectoptions
570
586
  id) {
571
- const { pk_type, pk_sql_type } = Table.pkSqlType(options.fields);
587
+ if (this.constructor.read_only)
588
+ throw new Error("Read-only access");
589
+ const { pk_type, pk_sql_type } = options.provider_name
590
+ ? {}
591
+ : Table.pkSqlType(options.fields);
572
592
  const schema = db_1.default.getTenantSchemaPrefix();
573
593
  // create table in database
574
594
  if (!options.provider_name)
@@ -585,6 +605,7 @@ class Table {
585
605
  description: options.description || "",
586
606
  provider_name: options.provider_name,
587
607
  provider_cfg: options.provider_cfg,
608
+ updated_at: new Date(),
588
609
  };
589
610
  let pk_fld_id;
590
611
  if (!id) {
@@ -645,6 +666,8 @@ class Table {
645
666
  * @param table
646
667
  */
647
668
  static async createInDb(table) {
669
+ if (this.constructor.read_only)
670
+ throw new Error("Read-only access");
648
671
  const is_sqlite = db_1.default.isSQLite;
649
672
  const schema = db_1.default.getTenantSchemaPrefix();
650
673
  const { pk_sql_type } = Table.pkSqlType(table.fields);
@@ -676,6 +699,8 @@ class Table {
676
699
  */
677
700
  // tbd check all other tables related to table description
678
701
  async delete(only_forget = false) {
702
+ if (this.constructor.read_only)
703
+ throw new Error("Read-only access");
679
704
  const schema = db_1.default.getTenantSchemaPrefix();
680
705
  const is_sqlite = db_1.default.isSQLite;
681
706
  await this.update({ ownership_field_id: null });
@@ -708,6 +733,8 @@ class Table {
708
733
  * Reset Sequence
709
734
  */
710
735
  async resetSequence() {
736
+ if (this.constructor.read_only)
737
+ throw new Error("Read-only access");
711
738
  const fields = this.fields;
712
739
  const pk = fields.find((f) => f.primary_key);
713
740
  if (!pk) {
@@ -726,13 +753,14 @@ class Table {
726
753
  * @param forRead
727
754
  */
728
755
  updateWhereWithOwnership(where, user, forRead) {
729
- const role = user?.role_id;
756
+ let use_user = this.constructor.fixed_user || user;
757
+ const role = use_user?.role_id;
730
758
  const min_role = forRead ? this.min_role_read : this.min_role_write;
731
759
  if (role &&
732
760
  role > min_role &&
733
761
  ((!this.ownership_field_id && !this.ownership_formula) || role === 100))
734
762
  return { notAuthorized: true };
735
- if (user &&
763
+ if (use_user &&
736
764
  role &&
737
765
  role < 100 &&
738
766
  role > min_role &&
@@ -741,16 +769,16 @@ class Table {
741
769
  if (!owner_field)
742
770
  throw new Error(`Owner field in table ${this.name} not found`);
743
771
  mergeIntoWhere(where, {
744
- [owner_field.name]: user.id,
772
+ [owner_field.name]: use_user.id,
745
773
  });
746
774
  }
747
- else if (user &&
775
+ else if (use_user &&
748
776
  role &&
749
777
  role < 100 &&
750
778
  role > min_role &&
751
779
  this.ownership_formula) {
752
780
  try {
753
- mergeIntoWhere(where, this.ownership_formula_where(user));
781
+ mergeIntoWhere(where, this.ownership_formula_where(use_user));
754
782
  }
755
783
  catch (e) {
756
784
  //ignore, ownership formula is too difficult to merge with where
@@ -758,23 +786,50 @@ class Table {
758
786
  }
759
787
  }
760
788
  }
761
- async addDeleteSyncInfo(ids, timestamp) {
789
+ async addDeleteSyncInfo(ids, timestamp, ownerFieldName, ownershipFormula) {
790
+ if (this.constructor.read_only)
791
+ throw new Error("Read-only access");
792
+ // Top-level keys referenced by the formula (strip join path suffixes,
793
+ // drop "user"). Stored in owner_fields so is_owner() can re-evaluate later.
794
+ const formulaTopKeys = ownershipFormula
795
+ ? [
796
+ ...new Set([...freeVariables(ownershipFormula)]
797
+ .map((v) => v.split(/[.Ⱶ]/)[0])
798
+ .filter((v) => v !== "user")),
799
+ ]
800
+ : null;
762
801
  await db_1.default.tryCatchInTransaction(async () => {
763
802
  if (ids.length > 0) {
764
803
  const schema = db_1.default.getTenantSchemaPrefix();
765
804
  const pkName = this.pk_name || "id";
766
805
  if (isNode()) {
767
- await db_1.default.query(`delete from ${schema}"${db_1.default.sqlsanitize(this.name)}_sync_info" where ref in (
768
- ${ids.map((row) => row[pkName]).join(",")})`);
769
- await db_1.default.query(`insert into ${schema}"${db_1.default.sqlsanitize(this.name)}_sync_info" (ref, last_modified, deleted)
770
- values ${ids
771
- .map((row) => `(${row[pkName]}, date_trunc('milliseconds', to_timestamp( ${timestamp.valueOf() / 1000.0} ) ), true)`)
772
- .join(",")}`);
806
+ const pkVals = ids.map((row) => String(row[pkName]));
807
+ await db_1.default.query(`delete from ${schema}"${db_1.default.sqlsanitize(this.name)}_sync_info" where ref = ANY($1::text[])`, [pkVals]);
808
+ const tsParam = timestamp.valueOf() / 1000.0;
809
+ const insertParams = [tsParam];
810
+ const valueClauses = pkVals.map((pkVal, i) => {
811
+ const ownerVal = ownerFieldName
812
+ ? (ids[i][ownerFieldName] ?? null)
813
+ : null;
814
+ const ownerFieldsVal = formulaTopKeys && formulaTopKeys.length > 0
815
+ ? JSON.stringify(Object.fromEntries(formulaTopKeys.map((k) => [k, ids[i][k] ?? null])))
816
+ : null;
817
+ insertParams.push(pkVal);
818
+ insertParams.push(ownerVal);
819
+ insertParams.push(ownerFieldsVal);
820
+ return `($${insertParams.length - 2}::text, date_trunc('milliseconds', to_timestamp($1)), true, $${insertParams.length - 1}, $${insertParams.length})`;
821
+ });
822
+ await db_1.default.query(`insert into ${schema}"${db_1.default.sqlsanitize(this.name)}_sync_info" (ref, last_modified, deleted, owner_id, owner_fields)
823
+ values ${valueClauses.join(",")}`, insertParams);
773
824
  }
774
825
  else {
826
+ const pkVals = ids.map((row) => String(row[pkName]));
827
+ const placeholders = pkVals
828
+ .map((_, i) => `$${i + 1}`)
829
+ .join(",");
775
830
  await db_1.default.query(`update "${db_1.default.sqlsanitize(this.name)}_sync_info"
776
831
  set deleted = true, modified_local = true
777
- where ref in (${ids.map((row) => row[pkName]).join(",")})`);
832
+ where ref in (${placeholders})`, pkVals);
778
833
  }
779
834
  }
780
835
  }, (e) => {
@@ -797,9 +852,12 @@ class Table {
797
852
  * @returns
798
853
  */
799
854
  async deleteRows(where, user, noTrigger, resultCollector) {
855
+ let use_user = this.constructor.fixed_user || user;
856
+ if (this.constructor.read_only)
857
+ throw new Error("Read-only access");
800
858
  //Fast truncate if user is admin and where is blank
801
859
  const cfields = await field_1.default.find({ reftable_name: this.name }, { cached: true });
802
- if ((!user || user?.role_id === 1) &&
860
+ if ((!use_user || use_user?.role_id === 1) &&
803
861
  Object.keys(where).length == 0 &&
804
862
  db_1.default.truncate &&
805
863
  noTrigger &&
@@ -815,7 +873,13 @@ class Table {
815
873
  // get triggers on delete
816
874
  const triggers = await trigger_1.default.getTableTriggers("Delete", this);
817
875
  const fields = this.fields;
818
- if (this.updateWhereWithOwnership(where, user)?.notAuthorized) {
876
+ const ownerFieldName = (() => {
877
+ if (!this.has_sync_info || !this.ownership_field_id)
878
+ return null;
879
+ const f = fields?.find((f) => f.id === this.ownership_field_id);
880
+ return f?.name ?? null;
881
+ })();
882
+ if (this.updateWhereWithOwnership(where, use_user)?.notAuthorized) {
819
883
  const state = require("../db/state").getState();
820
884
  state.log(4, `Not authorized to deleteRows in table ${this.name}.`);
821
885
  return;
@@ -827,13 +891,23 @@ class Table {
827
891
  expression: "__aggregation",
828
892
  attributes: { json: { table: this.name } },
829
893
  }, { cached: true });
894
+ // Join fields needed to snapshot ownership_formula values into sync_info.
895
+ // Computed once here; used either via the existing rows fetch (if it runs)
896
+ // or via a separate minimal fetch in the else branch below.
897
+ const ownershipJoinFields = {};
898
+ if (this.has_sync_info && this.ownership_formula) {
899
+ add_free_variables_to_joinfields(freeVariables(this.ownership_formula), ownershipJoinFields, fields);
900
+ }
830
901
  let rows;
831
902
  if (calc_agg_fields.length ||
832
- (user && user.role_id > this.min_role_write && this.ownership_formula)) {
903
+ (use_user &&
904
+ use_user.role_id > this.min_role_write &&
905
+ this.ownership_formula)) {
833
906
  rows = await this.getJoinedRows({
834
907
  where,
835
- forUser: user,
836
- forPublic: user?.role_id === 100,
908
+ forUser: use_user,
909
+ forPublic: use_user?.role_id === 100,
910
+ joinFields: ownershipJoinFields,
837
911
  });
838
912
  }
839
913
  const deleteFileFields = fields.filter((f) => f.type === "File" && f.attributes?.also_delete_file);
@@ -843,15 +917,15 @@ class Table {
843
917
  if (!rows)
844
918
  rows = await this.getJoinedRows({
845
919
  where,
846
- forUser: user,
847
- forPublic: user?.role_id === 100,
920
+ forUser: use_user,
921
+ forPublic: use_user?.role_id === 100,
848
922
  });
849
923
  for (const trigger of triggers) {
850
924
  for (const row of rows) {
851
925
  // run triggers on delete
852
- if (trigger.haltOnOnlyIf?.(row, user))
926
+ if (trigger.haltOnOnlyIf?.(row, use_user))
853
927
  continue;
854
- const runres = await trigger.run(row, { user });
928
+ const runres = await trigger.run(row, { user: use_user });
855
929
  if (runres && resultCollector)
856
930
  mergeActionResults(resultCollector, runres);
857
931
  }
@@ -871,32 +945,47 @@ class Table {
871
945
  }
872
946
  await db_1.default.tryCatchInTransaction(async () => {
873
947
  if (rows) {
874
- const delIds = rows.map((r) => r[this.pk_name]);
948
+ const pkIds = rows.map((r) => r[this.pk_name]);
875
949
  if (!db_1.default.isSQLite) {
876
950
  await db_1.default.deleteWhere(this.name, {
877
- [this.pk_name]: { in: delIds },
951
+ [this.pk_name]: { in: pkIds },
878
952
  });
879
953
  }
880
954
  else {
881
- await db_1.default.query(`delete from "${db_1.default.sqlsanitize(this.name)}" where "${db_1.default.sqlsanitize(this.pk_name)}" in (${delIds.join(",")})`);
955
+ await db_1.default.query(`delete from "${db_1.default.sqlsanitize(this.name)}" where "${db_1.default.sqlsanitize(this.pk_name)}" in (${pkIds.join(",")})`);
882
956
  }
883
957
  for (const row of rows)
884
958
  await this.auto_update_calc_aggregations(row);
885
959
  if (this.has_sync_info) {
886
960
  const dbTime = await db_1.default.time();
887
- await this.addDeleteSyncInfo(rows, dbTime);
961
+ await this.addDeleteSyncInfo(rows, dbTime, ownerFieldName, this.ownership_formula);
888
962
  }
889
963
  }
890
964
  else {
891
- const delIds = this.has_sync_info
892
- ? await db_1.default.select(this.name, where, {
893
- fields: [this.pk_name],
894
- })
895
- : null;
896
- await db_1.default.deleteWhere(this.name, where);
965
+ let preDeleteRows = null;
897
966
  if (this.has_sync_info) {
967
+ if (this.ownership_formula) {
968
+ // ownership_formula: fetch all fields (including join fields) so
969
+ // addDeleteSyncInfo can snapshot them into owner_fields.
970
+ preDeleteRows = await this.getJoinedRows({
971
+ where,
972
+ joinFields: ownershipJoinFields,
973
+ });
974
+ }
975
+ else {
976
+ // Simple case: fetch only the pk (and owner field if present).
977
+ const selectFields = ownerFieldName
978
+ ? [this.pk_name, ownerFieldName]
979
+ : [this.pk_name];
980
+ preDeleteRows = await db_1.default.select(this.name, where, {
981
+ fields: selectFields,
982
+ });
983
+ }
984
+ }
985
+ await db_1.default.deleteWhere(this.name, where);
986
+ if (this.has_sync_info && preDeleteRows) {
898
987
  const dbTime = await db_1.default.time();
899
- await this.addDeleteSyncInfo(delIds, dbTime);
988
+ await this.addDeleteSyncInfo(preDeleteRows, dbTime, ownerFieldName, this.ownership_formula);
900
989
  }
901
990
  }
902
991
  //if (fields.find((f) => f.primary_key)) await this.resetSequence();
@@ -932,7 +1021,7 @@ class Table {
932
1021
  if (this.fields) {
933
1022
  for (const f of this.fields) {
934
1023
  if (f.type && (0, common_types_1.instanceOfType)(f.type) && f.type.readFromDB)
935
- row[f.name] = f.type.readFromDB(row[f.name]);
1024
+ row[f.name] = f.type.readFromDB(row[f.name], f);
936
1025
  }
937
1026
  }
938
1027
  return row;
@@ -972,7 +1061,8 @@ class Table {
972
1061
  async getRow(where = {}, selopts = {}) {
973
1062
  const fields = this.fields;
974
1063
  const { forUser, forPublic, ...selopts1 } = selopts;
975
- const role = forUser ? forUser.role_id : forPublic ? 100 : null;
1064
+ const use_forUser = this.constructor.fixed_user || forUser;
1065
+ const role = use_forUser ? use_forUser.role_id : forPublic ? 100 : null;
976
1066
  this.normalise_fkey_values(where);
977
1067
  const row = await db_1.default.selectMaybeOne(this.name, where, this.processSelectOptions(selopts1));
978
1068
  if (!row || !this.fields)
@@ -985,11 +1075,11 @@ class Table {
985
1075
  const owner_field = fields.find((f) => f.id === this.ownership_field_id);
986
1076
  if (!owner_field)
987
1077
  throw new Error(`Owner field in table ${this.name} not found`);
988
- if (row[owner_field.name] !== forUser.id)
1078
+ if (row[owner_field.name] !== use_forUser.id)
989
1079
  return null;
990
1080
  }
991
1081
  else if (this.ownership_formula || this.name === "users") {
992
- if (!this.is_owner(forUser, row))
1082
+ if (!this.is_owner(use_forUser, row))
993
1083
  return null;
994
1084
  }
995
1085
  else
@@ -1027,10 +1117,10 @@ class Table {
1027
1117
  if (!this.fields)
1028
1118
  return [];
1029
1119
  const { forUser, forPublic, ...selopts1 } = selopts;
1030
- const role = forUser ? forUser.role_id : forPublic ? 100 : null;
1120
+ const use_forUser = this.constructor.fixed_user || forUser;
1121
+ const role = use_forUser ? use_forUser.role_id : forPublic ? 100 : null;
1031
1122
  if (role &&
1032
- this.updateWhereWithOwnership(where, forUser || { role_id: 100 }, true)
1033
- ?.notAuthorized) {
1123
+ this.updateWhereWithOwnership(where, use_forUser || { role_id: 100 }, true)?.notAuthorized) {
1034
1124
  return [];
1035
1125
  }
1036
1126
  this.normalise_fkey_values(where);
@@ -1043,7 +1133,8 @@ class Table {
1043
1133
  //already dealt with by changing where
1044
1134
  }
1045
1135
  else if (this.ownership_formula || this.name === "users") {
1046
- rows = rows.filter((row) => this.is_owner(forUser, row));
1136
+ if (!selopts?.disable_ownership_postqfilter)
1137
+ rows = rows.filter((row) => this.is_owner(use_forUser, row));
1047
1138
  }
1048
1139
  else
1049
1140
  return []; //no ownership
@@ -1093,9 +1184,20 @@ class Table {
1093
1184
  * @param fieldnm
1094
1185
  * @returns {Promise<Object[]>}
1095
1186
  */
1096
- async distinctValues(fieldnm, whereObj) {
1097
- if (whereObj) {
1098
- const { where, values } = (0, internal_1.mkWhere)(whereObj, db_1.default.isSQLite);
1187
+ async distinctValues(fieldnm, whereObj, user) {
1188
+ const useWhere = { ...(whereObj || {}) };
1189
+ if (user &&
1190
+ user.role_id > this.min_role_read &&
1191
+ !(this.ownership_field_id || this.ownership_formula)) {
1192
+ return [];
1193
+ }
1194
+ if (user &&
1195
+ user.role_id > this.min_role_read &&
1196
+ (this.ownership_field_id || this.ownership_formula)) {
1197
+ this.updateWhereWithOwnership(useWhere, user, true);
1198
+ }
1199
+ if (Object.keys(useWhere).length) {
1200
+ const { where, values } = (0, internal_1.mkWhere)(useWhere, db_1.default.isSQLite);
1099
1201
  const res = await db_1.default.query(`select distinct "${db_1.default.sqlsanitize(fieldnm)}" from ${this.sql_name} ${where} order by "${db_1.default.sqlsanitize(fieldnm)}" limit 1000`, values);
1100
1202
  return res.rows.map((r) => r[fieldnm]);
1101
1203
  }
@@ -1155,6 +1257,9 @@ class Table {
1155
1257
  * @returns
1156
1258
  */
1157
1259
  async updateRow(v_in, id_in, user, noTrigger, resultCollector, restore_of_version, syncTimestamp, additionalTriggerValues, autoRecalcIterations, extraArgs) {
1260
+ let use_user = this.constructor.fixed_user || user;
1261
+ if (this.constructor.read_only)
1262
+ throw new Error("Read-only access");
1158
1263
  // migrating to options arg
1159
1264
  if (typeof noTrigger === "object") {
1160
1265
  const extraOptions = noTrigger;
@@ -1188,7 +1293,7 @@ class Table {
1188
1293
  ]);
1189
1294
  const fields = this.fields;
1190
1295
  const pk_name = this.pk_name;
1191
- const role = user?.role_id;
1296
+ const role = use_user?.role_id;
1192
1297
  const state = require("../db/state").getState();
1193
1298
  let stringified = false;
1194
1299
  const sqliteJsonCols = !isNode()
@@ -1206,7 +1311,7 @@ class Table {
1206
1311
  }
1207
1312
  if (this.ownership_formula)
1208
1313
  add_free_variables_to_joinfields(freeVariables(this.ownership_formula), joinFields, fields);
1209
- if (user &&
1314
+ if (use_user &&
1210
1315
  role &&
1211
1316
  (role > this.min_role_write || role > this.min_role_read)) {
1212
1317
  if (role === 100)
@@ -1215,19 +1320,19 @@ class Table {
1215
1320
  const owner_field = fields.find((f) => f.id === this.ownership_field_id);
1216
1321
  if (!owner_field)
1217
1322
  throw new Error(`Owner field in table ${this.name} not found`);
1218
- if (v[owner_field.name] && v[owner_field.name] != user.id) {
1219
- state.log(4, `Not authorized to updateRow in table ${this.name}. ${user.id} does not match owner field in updates`);
1323
+ if (v[owner_field.name] && v[owner_field.name] != use_user.id) {
1324
+ state.log(4, `Not authorized to updateRow in table ${this.name}. ${use_user.id} does not match owner field in updates`);
1220
1325
  return "Not authorized";
1221
1326
  }
1222
1327
  //need to check existing
1223
1328
  if (!existing)
1224
1329
  existing = await this.getJoinedRow({
1225
1330
  where: { [pk_name]: id },
1226
- forUser: user,
1331
+ forUser: use_user,
1227
1332
  joinFields,
1228
1333
  });
1229
- if (!existing || existing?.[owner_field.name] !== user.id) {
1230
- state.log(4, `Not authorized to updateRow in table ${this.name}. ${user.id} does not match owner field in exisiting`);
1334
+ if (!existing || existing?.[owner_field.name] !== use_user.id) {
1335
+ state.log(4, `Not authorized to updateRow in table ${this.name}. ${use_user.id} does not match owner field in exisiting`);
1231
1336
  return "Not authorized";
1232
1337
  }
1233
1338
  }
@@ -1235,11 +1340,11 @@ class Table {
1235
1340
  if (!existing)
1236
1341
  existing = await this.getJoinedRow({
1237
1342
  where: { [pk_name]: id },
1238
- forUser: user,
1343
+ forUser: use_user,
1239
1344
  joinFields,
1240
1345
  });
1241
- if (!existing || !this.is_owner(user, existing)) {
1242
- state.log(4, `Not authorized to updateRow in table ${this.name}. User does not match formula: ${JSON.stringify(user)}`);
1346
+ if (!existing || !this.is_owner(use_user, existing)) {
1347
+ state.log(4, `Not authorized to updateRow in table ${this.name}. User does not match formula: ${JSON.stringify(use_user)}`);
1243
1348
  return "Not authorized";
1244
1349
  }
1245
1350
  }
@@ -1252,7 +1357,7 @@ class Table {
1252
1357
  if (!existing)
1253
1358
  existing = await this.getJoinedRow({
1254
1359
  where: { [pk_name]: id },
1255
- forUser: user,
1360
+ forUser: use_user,
1256
1361
  joinFields,
1257
1362
  });
1258
1363
  const newRow = { ...existing, ...v };
@@ -1260,8 +1365,8 @@ class Table {
1260
1365
  if (constraint_check)
1261
1366
  return constraint_check;
1262
1367
  }
1263
- if (user) {
1264
- let field_write_check = this.check_field_write_role(v, user);
1368
+ if (use_user) {
1369
+ let field_write_check = this.check_field_write_role(v, use_user);
1265
1370
  if (field_write_check)
1266
1371
  return field_write_check;
1267
1372
  }
@@ -1270,11 +1375,11 @@ class Table {
1270
1375
  if (!existing)
1271
1376
  existing = await this.getJoinedRow({
1272
1377
  where: { [pk_name]: id },
1273
- forUser: user,
1378
+ forUser: use_user,
1274
1379
  joinFields,
1275
1380
  });
1276
1381
  const valResCollector = resultCollector || {};
1277
- await trigger_1.default.runTableTriggers("Validate", this, { ...(additionalTriggerValues || {}), ...existing, ...v }, valResCollector, user, { old_row: existing, updated_fields: v_in, ...(extraArgs || {}) });
1382
+ await trigger_1.default.runTableTriggers("Validate", this, { ...(additionalTriggerValues || {}), ...existing, ...v }, valResCollector, use_user, { old_row: existing, updated_fields: v_in, ...(extraArgs || {}) });
1278
1383
  if ("error" in valResCollector)
1279
1384
  return valResCollector.error;
1280
1385
  if ("set_fields" in valResCollector)
@@ -1286,7 +1391,7 @@ class Table {
1286
1391
  let need_to_update = Object.keys(v_in).some((k) => freeVarFKFields.has(k));
1287
1392
  existing = await this.getJoinedRow({
1288
1393
  where: { [pk_name]: id },
1289
- forUser: user,
1394
+ forUser: use_user,
1290
1395
  joinFields,
1291
1396
  });
1292
1397
  let updated;
@@ -1300,11 +1405,11 @@ class Table {
1300
1405
  });
1301
1406
  updated = await this.getJoinedRow({
1302
1407
  where: { [pk_name]: id },
1303
- forUser: user,
1408
+ forUser: use_user,
1304
1409
  joinFields,
1305
1410
  });
1306
1411
  }
1307
- let calced = await apply_calculated_fields_stored(need_to_update ? updated || {} : { ...existing, ...v_in }, this.fields, this);
1412
+ let calced = await apply_calculated_fields_stored(need_to_update ? updated || {} : { ...existing, ...v_in }, this.fields, this, use_user);
1308
1413
  for (const f of fields)
1309
1414
  if (f.calculated && f.stored) {
1310
1415
  if (typeof f.type !== "string" &&
@@ -1334,7 +1439,7 @@ class Table {
1334
1439
  pk_name,
1335
1440
  },
1336
1441
  _time: new Date(),
1337
- _userid: user?.id,
1442
+ _userid: use_user?.id,
1338
1443
  _restore_of_version: restore_of_version || null,
1339
1444
  });
1340
1445
  }
@@ -1343,7 +1448,7 @@ class Table {
1343
1448
  if (triggers.length > 0)
1344
1449
  existing = await this.getJoinedRow({
1345
1450
  where: { [pk_name]: id },
1346
- forUser: user,
1451
+ forUser: use_user,
1347
1452
  joinFields,
1348
1453
  });
1349
1454
  }
@@ -1364,7 +1469,7 @@ class Table {
1364
1469
  if (!existing && really_changed_field_names.size && keyChanged)
1365
1470
  existing = await this.getJoinedRow({
1366
1471
  where: { [pk_name]: id },
1367
- forUser: user,
1472
+ forUser: use_user,
1368
1473
  joinFields,
1369
1474
  });
1370
1475
  await db_1.default.update(this.name, v, id, {
@@ -1387,10 +1492,12 @@ class Table {
1387
1492
  await this.auto_update_calc_aggregations(existing, !existing, (autoRecalcIterations || 0) + 1, really_changed_field_names, keyChanged);
1388
1493
  }
1389
1494
  if (!noTrigger) {
1390
- await trigger_1.default.runTableTriggers("Update", this, { ...(additionalTriggerValues || {}), ...newRow }, resultCollector, role === 100 ? undefined : user, { old_row: existing, updated_fields: v_in, ...(extraArgs || {}) });
1495
+ await trigger_1.default.runTableTriggers("Update", this, { ...(additionalTriggerValues || {}), ...newRow }, resultCollector, role === 100 ? undefined : use_user, { old_row: existing, updated_fields: v_in, ...(extraArgs || {}) });
1391
1496
  }
1392
1497
  }
1393
1498
  static async analyze_all_indexed_tables() {
1499
+ if (this.constructor.read_only)
1500
+ throw new Error("Read-only access");
1394
1501
  const tables = await Table.find({}, { cached: true });
1395
1502
  const schemaPrefix = db_1.default.getTenantSchemaPrefix();
1396
1503
  for (const table of tables)
@@ -1398,6 +1505,8 @@ class Table {
1398
1505
  await db_1.default.query(`analyze ${schemaPrefix}"${(0, internal_1.sqlsanitize)(table.name)}";`);
1399
1506
  }
1400
1507
  async insert_history_row(v0, retry = 0) {
1508
+ if (this.constructor.read_only)
1509
+ throw new Error("Read-only access");
1401
1510
  // sometimes there is a race condition in history inserts
1402
1511
  // https://dba.stackexchange.com/questions/212580/concurrent-transactions-result-in-race-condition-with-unique-constraint-on-inser
1403
1512
  // solution: retry 3 times, if fails run with on conflict do nothing
@@ -1442,7 +1551,8 @@ class Table {
1442
1551
  SELECT MAX(last_modified) "last_modified", ref
1443
1552
  FROM ${schema}"${db_1.default.sqlsanitize(this.name)}_sync_info"
1444
1553
  GROUP BY ref HAVING ref = ($1)`;
1445
- const result = await db_1.default.query(sql, db_1.default.isSQLite ? ids : [ids]);
1554
+ const strIds = ids.map((id) => String(id));
1555
+ const result = await db_1.default.query(sql, db_1.default.isSQLite ? strIds : [strIds]);
1446
1556
  return result.rows;
1447
1557
  }, (e) => {
1448
1558
  require("../db/state")
@@ -1452,6 +1562,8 @@ class Table {
1452
1562
  });
1453
1563
  }
1454
1564
  async insertSyncInfo(id, updates, syncTimestamp) {
1565
+ if (this.constructor.read_only)
1566
+ throw new Error("Read-only access");
1455
1567
  await db_1.default.tryCatchInTransaction(async () => {
1456
1568
  const schema = db_1.default.getTenantSchemaPrefix();
1457
1569
  if (isNode()) {
@@ -1462,15 +1574,15 @@ class Table {
1462
1574
  }
1463
1575
  await db_1.default.query(`insert into ${schema}"${db_1.default.sqlsanitize(this.name)}_sync_info" (ref, last_modified, updated_fields)
1464
1576
  values(
1465
- $1,
1577
+ $1::text,
1466
1578
  date_trunc('milliseconds', to_timestamp($2)),
1467
1579
  $3::jsonb
1468
1580
  )`, [id, timestamp.valueOf() / 1000.0, JSON.stringify(fieldTimestamps)]);
1469
1581
  }
1470
1582
  else {
1471
1583
  await db_1.default.query(`insert into "${db_1.default.sqlsanitize(this.name)}_sync_info"
1472
- (ref, modified_local, deleted)
1473
- values('${id}', true, false)`);
1584
+ (ref, modified_local, deleted)
1585
+ values(CAST($1 AS TEXT), true, false)`, [id]);
1474
1586
  }
1475
1587
  }, (e) => {
1476
1588
  require("../db/state")
@@ -1479,6 +1591,8 @@ class Table {
1479
1591
  });
1480
1592
  }
1481
1593
  async updateSyncInfo(id, v, oldLastModified, syncTimestamp) {
1594
+ if (this.constructor.read_only)
1595
+ throw new Error("Read-only access");
1482
1596
  await db_1.default.tryCatchInTransaction(async () => {
1483
1597
  const schema = db_1.default.getTenantSchemaPrefix();
1484
1598
  if (!db_1.default.isSQLite) {
@@ -1489,13 +1603,13 @@ class Table {
1489
1603
  for (const k of Object.keys(v).filter((key) => key !== this.pk_name)) {
1490
1604
  fieldTimestamps[k] = timestamp;
1491
1605
  }
1492
- await db_1.default.query(`update ${schema}"${db_1.default.sqlsanitize(this.name)}_sync_info"
1493
- set
1606
+ await db_1.default.query(`update ${schema}"${db_1.default.sqlsanitize(this.name)}_sync_info"
1607
+ set
1494
1608
  last_modified=date_trunc('milliseconds', to_timestamp($1)),
1495
1609
  updated_fields =
1496
1610
  coalesce(updated_fields, '{}'::jsonb) || $4::jsonb
1497
- where
1498
- ref=$2 and last_modified = to_timestamp($3)`, [
1611
+ where
1612
+ ref=$2::text and last_modified = to_timestamp($3)`, [
1499
1613
  timestamp.valueOf() / 1000.0,
1500
1614
  id,
1501
1615
  oldLastModified.valueOf() / 1000.0,
@@ -1503,8 +1617,8 @@ class Table {
1503
1617
  ]);
1504
1618
  }
1505
1619
  else {
1506
- await db_1.default.query(`update "${db_1.default.sqlsanitize(this.name)}_sync_info" set modified_local = true
1507
- where ref = ${id} and last_modified = ${oldLastModified ? oldLastModified.valueOf() : "null"}`);
1620
+ await db_1.default.query(`update "${db_1.default.sqlsanitize(this.name)}_sync_info" set modified_local = true
1621
+ where ref = CAST($1 AS TEXT) and last_modified = $2`, [id, oldLastModified ? oldLastModified.valueOf() : null]);
1508
1622
  }
1509
1623
  }, (e) => {
1510
1624
  require("../db/state")
@@ -1521,8 +1635,11 @@ class Table {
1521
1635
  * @returns {Promise<{error}|{success: boolean}>}
1522
1636
  */
1523
1637
  async tryUpdateRow(v, id, user, resultCollector, extraArgs) {
1638
+ if (this.constructor.read_only)
1639
+ throw new Error("Read-only access");
1640
+ let use_user = this.constructor.fixed_user || user;
1524
1641
  try {
1525
- const maybe_err = await this.updateRow(v, id, user, {
1642
+ const maybe_err = await this.updateRow(v, id, use_user, {
1526
1643
  noTrigger: false,
1527
1644
  resultCollector,
1528
1645
  extraArgs,
@@ -1543,9 +1660,12 @@ class Table {
1543
1660
  * @returns {Promise<void>}
1544
1661
  */
1545
1662
  async toggleBool(id, field_name, user) {
1663
+ let use_user = this.constructor.fixed_user || user;
1664
+ if (this.constructor.read_only)
1665
+ throw new Error("Read-only access");
1546
1666
  const row = await this.getRow({ [this.pk_name]: id });
1547
1667
  if (row)
1548
- await this.updateRow({ [field_name]: !row[field_name] }, id, user);
1668
+ await this.updateRow({ [field_name]: !row[field_name] }, id, use_user);
1549
1669
  }
1550
1670
  delete_url(row, moreQuery) {
1551
1671
  const comppk = this.composite_pk_names;
@@ -1608,10 +1728,11 @@ class Table {
1608
1728
  * @param user
1609
1729
  */
1610
1730
  check_field_write_role(row, user) {
1731
+ let use_user = this.constructor.fixed_user || user;
1611
1732
  for (const field of this.fields) {
1612
1733
  if (typeof row[field.name] !== "undefined" &&
1613
1734
  field.attributes?.min_role_write &&
1614
- user.role_id > +field.attributes?.min_role_write)
1735
+ use_user.role_id > +field.attributes?.min_role_write)
1615
1736
  return "Not authorized";
1616
1737
  }
1617
1738
  return undefined;
@@ -1627,6 +1748,15 @@ class Table {
1627
1748
  v_in[field.name] = v_in[field.name][pk];
1628
1749
  }
1629
1750
  }
1751
+ async run_trigger(trigger_name, row, user, extraArgs) {
1752
+ let use_user = this.constructor.fixed_user || user;
1753
+ const trigger = trigger_1.default.findOne({ name: trigger_name });
1754
+ return await trigger.runWithoutRow({
1755
+ row,
1756
+ user: use_user,
1757
+ ...(extraArgs || {}),
1758
+ });
1759
+ }
1630
1760
  /**
1631
1761
  * Insert row into the table. By passing in the user as
1632
1762
  * the second argument, it will check write rights. If a user object is not
@@ -1651,6 +1781,9 @@ class Table {
1651
1781
  * @returns
1652
1782
  */
1653
1783
  async insertRow(v_in0, user, resultCollector, noTrigger, syncTimestamp) {
1784
+ let use_user = this.constructor.fixed_user || user;
1785
+ if (this.constructor.read_only)
1786
+ throw new Error("Read-only access");
1654
1787
  const v_in = { ...v_in0 };
1655
1788
  const fields = this.fields;
1656
1789
  const pk_name = this.pk_name;
@@ -1667,13 +1800,13 @@ class Table {
1667
1800
  }
1668
1801
  : {};
1669
1802
  this.normalise_fkey_values(v_in);
1670
- if (user && user.role_id > this.min_role_write) {
1803
+ if (use_user && use_user.role_id > this.min_role_write) {
1671
1804
  if (this.ownership_field_id) {
1672
1805
  const owner_field = fields.find((f) => f.id === this.ownership_field_id);
1673
1806
  if (!owner_field)
1674
1807
  throw new Error(`Owner field in table ${this.name} not found`);
1675
- if (v_in[owner_field.name] != user.id) {
1676
- state.log(4, `Not authorized to insertRow in table ${this.name}. ${user.id} does not match owner field`);
1808
+ if (v_in[owner_field.name] != use_user.id) {
1809
+ state.log(4, `Not authorized to insertRow in table ${this.name}. ${use_user.id} does not match owner field`);
1677
1810
  return;
1678
1811
  }
1679
1812
  }
@@ -1685,27 +1818,53 @@ class Table {
1685
1818
  let constraint_check = this.check_table_constraints(v_in);
1686
1819
  if (constraint_check)
1687
1820
  throw new Error(constraint_check);
1688
- if (user) {
1689
- let field_write_check = this.check_field_write_role(v_in, user);
1821
+ if (use_user) {
1822
+ let field_write_check = this.check_field_write_role(v_in, use_user);
1690
1823
  if (field_write_check)
1691
1824
  return field_write_check;
1692
1825
  }
1693
1826
  //check validate here based on v_in
1694
1827
  const valResCollector = resultCollector || {};
1695
- await trigger_1.default.runTableTriggers("Validate", this, { ...v_in }, valResCollector, user);
1828
+ await trigger_1.default.runTableTriggers("Validate", this, { ...v_in }, valResCollector, use_user);
1696
1829
  if ("error" in valResCollector)
1697
1830
  return valResCollector; //???
1698
1831
  if ("set_fields" in valResCollector)
1699
1832
  Object.assign(v_in, valResCollector.set_fields);
1833
+ // Apply expression defaults for fields not provided or left null by the caller
1834
+ for (const field of fields) {
1835
+ if (!field.primary_key &&
1836
+ field.attributes?.default_expression &&
1837
+ (!(field.name in v_in) || v_in[field.name] == null)) {
1838
+ try {
1839
+ const exprFn = get_async_expression_function(field.attributes.default_expression, fields, {});
1840
+ v_in[field.name] = await exprFn(v_in, use_user);
1841
+ }
1842
+ catch (_e) {
1843
+ state.log(4, `Error applying default_expression for field ${field.name}: ${_e.message}`);
1844
+ }
1845
+ }
1846
+ }
1847
+ // On mobile (SQLite), PKs with a client-side default (e.g. UUID via the
1848
+ // uuid-type plugin's default_js) must be generated before the insert.
1849
+ if (!isNode() && v_in[pk_name] == null) {
1850
+ const pkField = fields?.find((f) => f.primary_key && !f.is_fkey);
1851
+ const defaultJs = pkField?.type?.primaryKey?.default_js;
1852
+ if (typeof defaultJs === "function") {
1853
+ v_in[pk_name] = defaultJs();
1854
+ }
1855
+ }
1700
1856
  if (Object.keys(joinFields).length > 0 ||
1701
1857
  fields.some((f) => f.expression === "__aggregation")) {
1702
1858
  state.log(6, `Inserting ${this.name} because join fields: ${JSON.stringify(v_in)}`);
1703
1859
  this.prepare_row_for_writing(v_in);
1704
1860
  id = await db_1.default.insert(this.name, v_in, { pk_name, ...sqliteJsonCols });
1861
+ // db.insert returns SQLite rowid, not the PK for non-integer PK types
1862
+ if (!isNode() && v_in[pk_name] != null)
1863
+ id = v_in[pk_name];
1705
1864
  let existing = await this.getJoinedRows({
1706
1865
  where: { [pk_name]: id },
1707
1866
  joinFields,
1708
- forUser: user,
1867
+ forUser: use_user,
1709
1868
  });
1710
1869
  if (!existing?.[0]) {
1711
1870
  //failed ownership test
@@ -1714,7 +1873,7 @@ class Table {
1714
1873
  state.log(4, `Not authorized to insertRow in table ${this.name}. Inserted row not retrieved.`);
1715
1874
  return;
1716
1875
  }
1717
- let calced = await apply_calculated_fields_stored(existing[0], fields, this);
1876
+ let calced = await apply_calculated_fields_stored(existing[0], fields, this, use_user);
1718
1877
  v = { ...v_in };
1719
1878
  for (const f of fields)
1720
1879
  if (f.calculated && f.stored)
@@ -1723,23 +1882,28 @@ class Table {
1723
1882
  await db_1.default.update(this.name, v, id, { pk_name, ...sqliteJsonCols });
1724
1883
  }
1725
1884
  else {
1726
- v = await apply_calculated_fields_stored(v_in, fields, this);
1885
+ v = await apply_calculated_fields_stored(v_in, fields, this, use_user);
1727
1886
  this.prepare_row_for_writing(v);
1728
1887
  state.log(6, `Inserting ${this.name} row: ${JSON.stringify(v)}`);
1729
1888
  id = await db_1.default.insert(this.name, v, {
1730
1889
  pk_name,
1731
1890
  ...sqliteJsonCols,
1732
1891
  });
1892
+ // db.insert returns SQLite rowid, not the PK for non-integer PK types
1893
+ if (!isNode() && v[pk_name] != null)
1894
+ id = v[pk_name];
1733
1895
  }
1734
- if (user && user.role_id > this.min_role_write && this.ownership_formula) {
1896
+ if (use_user &&
1897
+ use_user.role_id > this.min_role_write &&
1898
+ this.ownership_formula) {
1735
1899
  let existing = await this.getJoinedRow({
1736
1900
  where: { [pk_name]: id },
1737
1901
  joinFields,
1738
- forUser: user,
1902
+ forUser: use_user,
1739
1903
  });
1740
- if (!existing || !this.is_owner(user, existing)) {
1904
+ if (!existing || !this.is_owner(use_user, existing)) {
1741
1905
  await this.deleteRows({ [pk_name]: id });
1742
- state.log(4, `Not authorized to insertRow in table ${this.name}. User does not match formula: ${JSON.stringify(user)}`);
1906
+ state.log(4, `Not authorized to insertRow in table ${this.name}. User does not match formula: ${JSON.stringify(use_user)}`);
1743
1907
  return;
1744
1908
  }
1745
1909
  }
@@ -1751,7 +1915,7 @@ class Table {
1751
1915
  next_version_by_id: id,
1752
1916
  pk_name,
1753
1917
  },
1754
- _userid: user?.id,
1918
+ _userid: use_user?.id,
1755
1919
  _time: new Date(),
1756
1920
  });
1757
1921
  if (this.has_sync_info) {
@@ -1759,15 +1923,15 @@ class Table {
1759
1923
  if (isNode()) {
1760
1924
  // sync_info for insert
1761
1925
  const schemaPrefix = db_1.default.getTenantSchemaPrefix();
1926
+ const tsParam = (syncTimestamp ? syncTimestamp : await db_1.default.time()).valueOf() /
1927
+ 1000.0;
1762
1928
  await db_1.default.query(`insert into ${schemaPrefix}"${db_1.default.sqlsanitize(this.name)}_sync_info"
1763
- (ref, last_modified) values(
1764
- ${id}, date_trunc('milliseconds', to_timestamp(${(syncTimestamp ? syncTimestamp : await db_1.default.time()).valueOf() /
1765
- 1000.0})))`);
1929
+ (ref, last_modified) values($1::text, date_trunc('milliseconds', to_timestamp($2)))`, [id, tsParam]);
1766
1930
  }
1767
1931
  else {
1768
1932
  await db_1.default.query(`insert into "${db_1.default.sqlsanitize(this.name)}_sync_info"
1769
1933
  (last_modified, ref, modified_local, deleted)
1770
- values(NULL, ${id}, true, false)`);
1934
+ values(NULL, CAST($1 AS TEXT), true, false)`, [id]);
1771
1935
  }
1772
1936
  }, (e) => {
1773
1937
  state.log(2, `Error inserting sync info for table ${this.name}: ${e.message}`);
@@ -1779,11 +1943,13 @@ class Table {
1779
1943
  await this.auto_update_calc_aggregations(newRow);
1780
1944
  if (!noTrigger) {
1781
1945
  apply_calculated_fields([newRow], this.fields);
1782
- await trigger_1.default.runTableTriggers("Insert", this, newRow, resultCollector, user);
1946
+ await trigger_1.default.runTableTriggers("Insert", this, newRow, resultCollector, use_user);
1783
1947
  }
1784
1948
  return id;
1785
1949
  }
1786
1950
  async auto_update_calc_aggregations(v0, refetch, iterations = 1, changedFields, keyChanged = false) {
1951
+ if (this.constructor.read_only)
1952
+ throw new Error("Read-only access");
1787
1953
  const state = require("../db/state").getState();
1788
1954
  const pk_name = this.pk_name;
1789
1955
  state.log(6, `auto_update_calc_aggregations table=${this.name} id=${v0[pk_name]} iters=${iterations}${changedFields ? ` changedFields=${[...(changedFields || [])]}` : ""}`);
@@ -1915,8 +2081,11 @@ class Table {
1915
2081
  * @returns {Promise<{error}|{success: *}>}
1916
2082
  */
1917
2083
  async tryInsertRow(v, user, resultCollector) {
2084
+ let use_user = this.constructor.fixed_user || user;
2085
+ if (this.constructor.read_only)
2086
+ throw new Error("Read-only access");
1918
2087
  try {
1919
- const id = await this.insertRow(v, user, resultCollector);
2088
+ const id = await this.insertRow(v, use_user, resultCollector);
1920
2089
  if (!id)
1921
2090
  return { error: "Not authorized" };
1922
2091
  if (id?.includes?.("Not authorized"))
@@ -2064,6 +2233,8 @@ class Table {
2064
2233
  );`);
2065
2234
  }
2066
2235
  async create_sync_info_table() {
2236
+ if (this.constructor.read_only)
2237
+ throw new Error("Read-only access");
2067
2238
  await db_1.default.tryCatchInTransaction(async () => {
2068
2239
  const schemaPrefix = db_1.default.getTenantSchemaPrefix();
2069
2240
  const fields = this.fields;
@@ -2072,10 +2243,12 @@ class Table {
2072
2243
  throw new Error("Unable to find a field with a primary key.");
2073
2244
  }
2074
2245
  await db_1.default.query(`create table ${schemaPrefix}"${(0, internal_1.sqlsanitize)(this.name)}_sync_info" (
2075
- ref integer,
2246
+ ref text not null,
2076
2247
  last_modified timestamp,
2077
2248
  deleted boolean default false,
2078
- updated_fields jsonb)`);
2249
+ updated_fields jsonb,
2250
+ owner_id integer,
2251
+ owner_fields jsonb)`);
2079
2252
  await db_1.default.query(`create index "${(0, internal_1.sqlsanitize)(this.name)}_sync_info_ref_index" on ${schemaPrefix}"${(0, internal_1.sqlsanitize)(this.name)}_sync_info"(ref)`);
2080
2253
  await db_1.default.query(`create index "${(0, internal_1.sqlsanitize)(this.name)}_sync_info_lm_index" on ${schemaPrefix}"${(0, internal_1.sqlsanitize)(this.name)}_sync_info"(last_modified)`);
2081
2254
  await db_1.default.query(`create index "${(0, internal_1.sqlsanitize)(this.name)}_sync_info_deleted_index" on ${schemaPrefix}"${(0, internal_1.sqlsanitize)(this.name)}_sync_info"(deleted)`);
@@ -2086,6 +2259,8 @@ class Table {
2086
2259
  });
2087
2260
  }
2088
2261
  async drop_sync_table() {
2262
+ if (this.constructor.read_only)
2263
+ throw new Error("Read-only access");
2089
2264
  await db_1.default.tryCatchInTransaction(async () => {
2090
2265
  const schemaPrefix = db_1.default.getTenantSchemaPrefix();
2091
2266
  await db_1.default.query(`
@@ -2103,6 +2278,9 @@ class Table {
2103
2278
  * @param user
2104
2279
  */
2105
2280
  async restore_row_version(id, version, user) {
2281
+ let use_user = this.constructor.fixed_user || user;
2282
+ if (this.constructor.read_only)
2283
+ throw new Error("Read-only access");
2106
2284
  const row = await db_1.default.selectOne(`${db_1.default.sqlsanitize(this.name)}__history`, {
2107
2285
  [this.pk_name]: id,
2108
2286
  _version: version,
@@ -2113,7 +2291,7 @@ class Table {
2113
2291
  r[f.name] = row[f.name];
2114
2292
  });
2115
2293
  //console.log("restore_row_version", r);
2116
- await this.updateRow(r, id, user, false, undefined, version);
2294
+ await this.updateRow(r, id, use_user, false, undefined, version);
2117
2295
  }
2118
2296
  /**
2119
2297
  * Undo row chnages
@@ -2121,6 +2299,9 @@ class Table {
2121
2299
  * @param user
2122
2300
  */
2123
2301
  async undo_row_changes(id, user) {
2302
+ let use_user = this.constructor.fixed_user || user;
2303
+ if (this.constructor.read_only)
2304
+ throw new Error("Read-only access");
2124
2305
  const current_version_row = await db_1.default.selectMaybeOne(`${(0, internal_1.sqlsanitize)(this.name)}__history`, { [this.pk_name]: id }, { orderBy: "_version", orderDesc: true, limit: 1 });
2125
2306
  //get max that is not a restore
2126
2307
  const last_non_restore = await db_1.default.selectMaybeOne(`${(0, internal_1.sqlsanitize)(this.name)}__history`, {
@@ -2132,7 +2313,7 @@ class Table {
2132
2313
  },
2133
2314
  }, { orderBy: "_version", orderDesc: true, limit: 1 });
2134
2315
  if (last_non_restore) {
2135
- await this.restore_row_version(id, last_non_restore._version, user);
2316
+ await this.restore_row_version(id, last_non_restore._version, use_user);
2136
2317
  }
2137
2318
  }
2138
2319
  /**
@@ -2141,6 +2322,9 @@ class Table {
2141
2322
  * @param user
2142
2323
  */
2143
2324
  async redo_row_changes(id, user) {
2325
+ let use_user = this.constructor.fixed_user || user;
2326
+ if (this.constructor.read_only)
2327
+ throw new Error("Read-only access");
2144
2328
  const current_version_row = await db_1.default.selectMaybeOne(`${(0, internal_1.sqlsanitize)(this.name)}__history`, { [this.pk_name]: id }, { orderBy: "_version", orderDesc: true, limit: 1 });
2145
2329
  if (current_version_row._restore_of_version) {
2146
2330
  const next_version = await db_1.default.selectMaybeOne(`${(0, internal_1.sqlsanitize)(this.name)}__history`, {
@@ -2150,7 +2334,7 @@ class Table {
2150
2334
  },
2151
2335
  }, { orderBy: "_version", limit: 1 });
2152
2336
  if (next_version) {
2153
- await this.restore_row_version(id, next_version._version, user);
2337
+ await this.restore_row_version(id, next_version._version, use_user);
2154
2338
  }
2155
2339
  }
2156
2340
  }
@@ -2159,6 +2343,8 @@ class Table {
2159
2343
  * with options object, or just minimal interval for legacy code
2160
2344
  */
2161
2345
  async compress_history(options) {
2346
+ if (this.constructor.read_only)
2347
+ throw new Error("Read-only access");
2162
2348
  const interval_secs = typeof options === "number" ? options : options?.interval_secs;
2163
2349
  const schemaPrefix = db_1.default.getTenantSchemaPrefix();
2164
2350
  const pk = this.pk_name;
@@ -2205,6 +2391,8 @@ class Table {
2205
2391
  */
2206
2392
  async drop_history_table() {
2207
2393
  const schemaPrefix = db_1.default.getTenantSchemaPrefix();
2394
+ if (this.constructor.read_only)
2395
+ throw new Error("Read-only access");
2208
2396
  await db_1.default.query(`
2209
2397
  drop table ${schemaPrefix}"${(0, internal_1.sqlsanitize)(this.name)}__history";`);
2210
2398
  }
@@ -2214,6 +2402,8 @@ class Table {
2214
2402
  * @returns {Promise<void>}
2215
2403
  */
2216
2404
  async rename(new_name) {
2405
+ if (this.constructor.read_only)
2406
+ throw new Error("Read-only access");
2217
2407
  //in transaction
2218
2408
  if (db_1.default.isSQLite)
2219
2409
  throw new InvalidAdminAction("Cannot rename table on SQLite");
@@ -2238,6 +2428,8 @@ class Table {
2238
2428
  * @returns {Promise<void>}
2239
2429
  */
2240
2430
  async update(new_table_rec) {
2431
+ if (this.constructor.read_only)
2432
+ throw new Error("Read-only access");
2241
2433
  if (new_table_rec.ownership_field_id === "")
2242
2434
  delete new_table_rec.ownership_field_id;
2243
2435
  const existing = Table.findOne({ id: this.id });
@@ -2245,6 +2437,7 @@ class Table {
2245
2437
  throw new Error(`Unable to find table with id: ${this.id}`);
2246
2438
  }
2247
2439
  const { external, fields, constraints, ...upd_rec } = new_table_rec;
2440
+ upd_rec.updated_at = new Date();
2248
2441
  await db_1.default.update("_sc_tables", upd_rec, this.id);
2249
2442
  //limited refresh if we do not have a client
2250
2443
  if (!db_1.default.getRequestContext()?.client)
@@ -2290,6 +2483,8 @@ class Table {
2290
2483
  * @returns {Promise<void>}
2291
2484
  */
2292
2485
  async enable_fkey_constraints() {
2486
+ if (this.constructor.read_only)
2487
+ throw new Error("Read-only access");
2293
2488
  const fields = this.fields;
2294
2489
  for (const f of fields)
2295
2490
  await f.enable_fkey_constraint(this);
@@ -2301,6 +2496,8 @@ class Table {
2301
2496
  * @returns {Promise<{error: string}|{error: string}|{error: string}|{error: string}|{error: string}|{success: string}|{error: (string|string|*)}>}
2302
2497
  */
2303
2498
  static async create_from_csv(name, filePath) {
2499
+ if (this.constructor.read_only)
2500
+ throw new Error("Read-only access");
2304
2501
  let rows;
2305
2502
  const state = await require("../db/state").getState();
2306
2503
  try {
@@ -2472,6 +2669,9 @@ class Table {
2472
2669
  * @returns {Promise<{error: string}|{success: string}>}
2473
2670
  */
2474
2671
  async import_csv_file(filePath, options) {
2672
+ //todo user check
2673
+ if (this.constructor.read_only)
2674
+ throw new Error("Read-only access");
2475
2675
  if (typeof options === "boolean") {
2476
2676
  options = { recalc_stored: options };
2477
2677
  }
@@ -2556,6 +2756,10 @@ class Table {
2556
2756
  // start sql transaction
2557
2757
  if (!options?.no_transaction)
2558
2758
  await client.query("BEGIN");
2759
+ if (db_1.default.isSQLite)
2760
+ await client.query("PRAGMA defer_foreign_keys = ON");
2761
+ else
2762
+ await client.query("SET CONSTRAINTS ALL DEFERRED");
2559
2763
  const readStream = (0, fs_1.createReadStream)(filePath);
2560
2764
  const returnedRows = [];
2561
2765
  try {
@@ -2801,6 +3005,8 @@ ${rejectDetails}`,
2801
3005
  return v1;
2802
3006
  }
2803
3007
  async import_json_history_file(filePath) {
3008
+ if (this.constructor.read_only)
3009
+ throw new Error("Read-only access");
2804
3010
  return await (0, async_json_stream_1.default)(filePath, async (row) => {
2805
3011
  await this.insert_history_row(row);
2806
3012
  });
@@ -2812,6 +3018,8 @@ ${rejectDetails}`,
2812
3018
  * @returns {Promise<{error: string}|{success: string}>}
2813
3019
  */
2814
3020
  async import_json_file(filePath, skip_first_data_row) {
3021
+ if (this.constructor.read_only)
3022
+ throw new Error("Read-only access");
2815
3023
  const fields = this.fields;
2816
3024
  const pk_name = this.pk_name;
2817
3025
  const { readState } = require("../plugin-helper");
@@ -3087,10 +3295,10 @@ ${rejectDetails}`,
3087
3295
  async aggregationQuery(aggregations, options) {
3088
3296
  const { forUser, forPublic } = options || {};
3089
3297
  const role = forUser ? forUser.role_id : forPublic ? 100 : null;
3298
+ const use_forUser = this.constructor.fixed_user || forUser;
3090
3299
  const where = { ...(options?.where || {}) };
3091
3300
  if (role &&
3092
- this.updateWhereWithOwnership(where, forUser || { role_id: 100 }, true)
3093
- ?.notAuthorized) {
3301
+ this.updateWhereWithOwnership(where, use_forUser || { role_id: 100 }, true)?.notAuthorized) {
3094
3302
  const emptyRet = {};
3095
3303
  Object.entries(aggregations).forEach(([nm, aggObj]) => {
3096
3304
  const agg = aggObj?.aggregate?.toLowerCase?.() || "count";
@@ -3114,9 +3322,10 @@ ${rejectDetails}`,
3114
3322
  return res.rows[0];
3115
3323
  }
3116
3324
  ownership_formula_where(user) {
3325
+ let use_user = this.constructor.fixed_user || user;
3117
3326
  if (!this.ownership_formula)
3118
3327
  return {};
3119
- const wh = jsexprToWhere(this.ownership_formula, { user }, this.fields);
3328
+ const wh = jsexprToWhere(this.ownership_formula, { user: use_user }, this.fields);
3120
3329
  if (wh.eq && Array.isArray(wh.eq)) {
3121
3330
  let arr = wh.eq;
3122
3331
  for (let index = 0; index < arr.length; index++) {
@@ -3152,7 +3361,8 @@ ${rejectDetails}`,
3152
3361
  ? `"${opts.schema}".`
3153
3362
  : db_1.default.getTenantSchemaPrefix();
3154
3363
  const { forUser, forPublic } = opts;
3155
- const role = forUser ? forUser.role_id : forPublic ? 100 : null;
3364
+ const use_forUser = this.constructor.fixed_user || forUser;
3365
+ const role = use_forUser ? use_forUser.role_id : forPublic ? 100 : null;
3156
3366
  if (role && role > this.min_role_read && this.ownership_formula) {
3157
3367
  const freeVars = freeVariables(this.ownership_formula);
3158
3368
  add_free_variables_to_joinfields(freeVars, joinFields, fields);
@@ -3166,17 +3376,17 @@ ${rejectDetails}`,
3166
3376
  if (!owner_field)
3167
3377
  throw new Error(`Owner field in table ${this.name} not found`);
3168
3378
  mergeIntoWhere(opts.where, {
3169
- [owner_field.name]: forUser.id,
3379
+ [owner_field.name]: use_forUser.id,
3170
3380
  });
3171
3381
  }
3172
- else if (forUser &&
3382
+ else if (use_forUser &&
3173
3383
  role &&
3174
3384
  role > this.min_role_read &&
3175
3385
  this.ownership_formula) {
3176
3386
  if (forPublic || role === 100)
3177
3387
  return { notAuthorized: true }; //TODO may not be true
3178
3388
  try {
3179
- mergeIntoWhere(opts.where, this.ownership_formula_where(forUser));
3389
+ mergeIntoWhere(opts.where, this.ownership_formula_where(use_forUser));
3180
3390
  }
3181
3391
  catch (e) {
3182
3392
  //ignore, ownership formula is too difficult to merge with where
@@ -3353,7 +3563,8 @@ ${rejectDetails}`,
3353
3563
  async getJoinedRows(opts = {}) {
3354
3564
  const fields = this.fields;
3355
3565
  const { forUser, forPublic, ...selopts1 } = opts;
3356
- const role = forUser ? forUser.role_id : forPublic ? 100 : null;
3566
+ const use_forUser = this.constructor.fixed_user || forUser;
3567
+ const role = use_forUser ? use_forUser.role_id : forPublic ? 100 : null;
3357
3568
  const { sql, values, notAuthorized, joinFields, aggregations } = await this.getJoinedQuery(opts);
3358
3569
  if (notAuthorized)
3359
3570
  return [];
@@ -3394,7 +3605,7 @@ ${rejectDetails}`,
3394
3605
  //already dealt with by changing where
3395
3606
  }
3396
3607
  else if (this.ownership_formula || this.name === "users") {
3397
- calcRow = calcRow.filter((row) => this.is_owner(forUser, row));
3608
+ calcRow = calcRow.filter((row) => this.is_owner(use_forUser, row));
3398
3609
  }
3399
3610
  else
3400
3611
  return []; //no ownership
@@ -3466,6 +3677,8 @@ ${rejectDetails}`,
3466
3677
  return (0, table_helper_1.get_formula_examples)(typename, this.fields.filter((f) => !f.calculated));
3467
3678
  }
3468
3679
  async repairCompositePrimary() {
3680
+ if (this.constructor.read_only)
3681
+ throw new Error("Read-only access");
3469
3682
  const primaryKeys = this.fields.filter((f) => f.primary_key);
3470
3683
  const nonSerialPKS = primaryKeys.some((f) => f.attributes?.NonSerial);
3471
3684
  const schemaPrefix = db_1.default.getTenantSchemaPrefix();
@@ -3507,6 +3720,8 @@ where table_schema = '${db_1.default.getTenantSchema() || "public"}'
3507
3720
  await Table.state_refresh(true);
3508
3721
  }
3509
3722
  async move_include_fts_to_search_context() {
3723
+ if (this.constructor.read_only)
3724
+ throw new Error("Read-only access");
3510
3725
  const include_fts_fields = this.fields.filter((f) => f.attributes?.include_fts);
3511
3726
  if (!include_fts_fields.length)
3512
3727
  return;
@@ -3543,6 +3758,8 @@ where table_schema = '${db_1.default.getTenantSchema() || "public"}'
3543
3758
  }
3544
3759
  }
3545
3760
  }
3761
+ Table.fixed_user = undefined;
3762
+ Table.read_only = false;
3546
3763
  async function dump_table_to_json_file(filePath, tableName) {
3547
3764
  const writeStream = (0, fs_1.createWriteStream)(filePath);
3548
3765
  const client = db_1.default.isSQLite ? db_1.default : await db_1.default.getClient();