@saltcorn/data 1.6.0-beta.1 → 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 (152) hide show
  1. package/dist/base-plugin/actions.d.ts +191 -62
  2. package/dist/base-plugin/actions.d.ts.map +1 -1
  3. package/dist/base-plugin/actions.js +164 -66
  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 +210 -65
  14. package/dist/base-plugin/index.d.ts.map +1 -1
  15. package/dist/base-plugin/types.d.ts +19 -9
  16. package/dist/base-plugin/types.d.ts.map +1 -1
  17. package/dist/base-plugin/types.js +41 -12
  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 +12 -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.js +2 -2
  26. package/dist/base-plugin/viewtemplates/filter.js.map +1 -1
  27. package/dist/base-plugin/viewtemplates/list.d.ts.map +1 -1
  28. package/dist/base-plugin/viewtemplates/list.js +14 -1
  29. package/dist/base-plugin/viewtemplates/list.js.map +1 -1
  30. package/dist/base-plugin/viewtemplates/room.d.ts.map +1 -1
  31. package/dist/base-plugin/viewtemplates/room.js +1 -1
  32. package/dist/base-plugin/viewtemplates/room.js.map +1 -1
  33. package/dist/base-plugin/viewtemplates/show.d.ts.map +1 -1
  34. package/dist/db/fixtures.d.ts.map +1 -1
  35. package/dist/db/fixtures.js +31 -1
  36. package/dist/db/fixtures.js.map +1 -1
  37. package/dist/db/state.d.ts +5 -3
  38. package/dist/db/state.d.ts.map +1 -1
  39. package/dist/db/state.js +36 -12
  40. package/dist/db/state.js.map +1 -1
  41. package/dist/migrate.d.ts.map +1 -1
  42. package/dist/migrate.js +3 -1
  43. package/dist/migrate.js.map +1 -1
  44. package/dist/migrations/202604091531.d.ts +2 -0
  45. package/dist/migrations/202604091531.d.ts.map +1 -0
  46. package/dist/migrations/202604091531.js +9 -0
  47. package/dist/migrations/202604091531.js.map +1 -0
  48. package/dist/migrations/202604111200.d.ts +2 -0
  49. package/dist/migrations/202604111200.d.ts.map +1 -0
  50. package/dist/migrations/202604111200.js +15 -0
  51. package/dist/migrations/202604111200.js.map +1 -0
  52. package/dist/migrations/202604141200.d.ts +2 -0
  53. package/dist/migrations/202604141200.d.ts.map +1 -0
  54. package/dist/migrations/202604141200.js +14 -0
  55. package/dist/migrations/202604141200.js.map +1 -0
  56. package/dist/mobile-mocks/node/fs/promises.d.ts +1 -0
  57. package/dist/mobile-mocks/node/fs/promises.d.ts.map +1 -1
  58. package/dist/mobile-mocks/node/fs/promises.js +4 -0
  59. package/dist/mobile-mocks/node/fs/promises.js.map +1 -1
  60. package/dist/mobile-mocks/node/fs.d.ts +2 -0
  61. package/dist/mobile-mocks/node/fs.d.ts.map +1 -1
  62. package/dist/mobile-mocks/node/fs.js +36 -0
  63. package/dist/mobile-mocks/node/fs.js.map +1 -1
  64. package/dist/mobile-mocks/npm/npm-registry-fetch.d.ts +3 -0
  65. package/dist/mobile-mocks/npm/npm-registry-fetch.d.ts.map +1 -0
  66. package/dist/mobile-mocks/npm/npm-registry-fetch.js +3 -0
  67. package/dist/mobile-mocks/npm/npm-registry-fetch.js.map +1 -0
  68. package/dist/mobile-mocks/saltcorn/admin-models-tenant.d.ts +1 -0
  69. package/dist/mobile-mocks/saltcorn/admin-models-tenant.d.ts.map +1 -0
  70. package/dist/mobile-mocks/saltcorn/admin-models-tenant.js +2 -0
  71. package/dist/mobile-mocks/saltcorn/admin-models-tenant.js.map +1 -0
  72. package/dist/mobile-mocks/saltcorn/plugins-loader-plugin-installer.d.ts +1 -0
  73. package/dist/mobile-mocks/saltcorn/plugins-loader-plugin-installer.d.ts.map +1 -0
  74. package/dist/mobile-mocks/saltcorn/plugins-loader-plugin-installer.js +2 -0
  75. package/dist/mobile-mocks/saltcorn/plugins-loader-plugin-installer.js.map +1 -0
  76. package/dist/mobile-mocks/saltcorn/plugins-loader-stable-versioning.d.ts +1 -0
  77. package/dist/mobile-mocks/saltcorn/plugins-loader-stable-versioning.d.ts.map +1 -0
  78. package/dist/mobile-mocks/saltcorn/plugins-loader-stable-versioning.js +2 -0
  79. package/dist/mobile-mocks/saltcorn/plugins-loader-stable-versioning.js.map +1 -0
  80. package/dist/models/config.d.ts.map +1 -1
  81. package/dist/models/config.js +46 -1
  82. package/dist/models/config.js.map +1 -1
  83. package/dist/models/discovery.d.ts +14 -0
  84. package/dist/models/discovery.d.ts.map +1 -1
  85. package/dist/models/discovery.js +63 -0
  86. package/dist/models/discovery.js.map +1 -1
  87. package/dist/models/expression.d.ts +7 -4
  88. package/dist/models/expression.d.ts.map +1 -1
  89. package/dist/models/expression.js +28 -14
  90. package/dist/models/expression.js.map +1 -1
  91. package/dist/models/field.d.ts.map +1 -1
  92. package/dist/models/field.js +39 -9
  93. package/dist/models/field.js.map +1 -1
  94. package/dist/models/index.d.ts +12 -2
  95. package/dist/models/index.d.ts.map +1 -1
  96. package/dist/models/index.js +1 -1
  97. package/dist/models/index.js.map +1 -1
  98. package/dist/models/page.d.ts +1 -0
  99. package/dist/models/page.d.ts.map +1 -1
  100. package/dist/models/page.js +36 -20
  101. package/dist/models/page.js.map +1 -1
  102. package/dist/models/plugin.d.ts +57 -0
  103. package/dist/models/plugin.d.ts.map +1 -1
  104. package/dist/models/plugin.js +322 -1
  105. package/dist/models/plugin.js.map +1 -1
  106. package/dist/models/scheduler.d.ts.map +1 -1
  107. package/dist/models/scheduler.js +15 -5
  108. package/dist/models/scheduler.js.map +1 -1
  109. package/dist/models/table.d.ts +3 -1
  110. package/dist/models/table.d.ts.map +1 -1
  111. package/dist/models/table.js +148 -44
  112. package/dist/models/table.js.map +1 -1
  113. package/dist/models/trigger.d.ts +3 -1
  114. package/dist/models/trigger.d.ts.map +1 -1
  115. package/dist/models/trigger.js +18 -4
  116. package/dist/models/trigger.js.map +1 -1
  117. package/dist/models/user.d.ts.map +1 -1
  118. package/dist/models/user.js +2 -1
  119. package/dist/models/user.js.map +1 -1
  120. package/dist/models/view.d.ts +1 -0
  121. package/dist/models/view.d.ts.map +1 -1
  122. package/dist/models/view.js +74 -14
  123. package/dist/models/view.js.map +1 -1
  124. package/dist/models/workflow_run.d.ts.map +1 -1
  125. package/dist/models/workflow_run.js +3 -2
  126. package/dist/models/workflow_run.js.map +1 -1
  127. package/dist/models/workflow_step.d.ts +1 -1
  128. package/dist/models/workflow_step.d.ts.map +1 -1
  129. package/dist/models/workflow_step.js +2 -1
  130. package/dist/models/workflow_step.js.map +1 -1
  131. package/dist/plugin-helper.d.ts +3 -11
  132. package/dist/plugin-helper.d.ts.map +1 -1
  133. package/dist/plugin-helper.js +102 -60
  134. package/dist/plugin-helper.js.map +1 -1
  135. package/dist/standard-menu.d.ts.map +1 -1
  136. package/dist/standard-menu.js +19 -0
  137. package/dist/standard-menu.js.map +1 -1
  138. package/dist/tests/remote_query_helper.js +1 -1
  139. package/dist/tests/remote_query_helper.js.map +1 -1
  140. package/dist/utils.d.ts +15 -0
  141. package/dist/utils.d.ts.map +1 -1
  142. package/dist/utils.js +102 -5
  143. package/dist/utils.js.map +1 -1
  144. package/dist/viewable_fields.d.ts +1 -1
  145. package/dist/viewable_fields.d.ts.map +1 -1
  146. package/dist/viewable_fields.js +48 -9
  147. package/dist/viewable_fields.js.map +1 -1
  148. package/dist/web-mobile-commons.d.ts.map +1 -1
  149. package/dist/web-mobile-commons.js +2 -1
  150. package/dist/web-mobile-commons.js.map +1 -1
  151. package/package.json +12 -9
  152. package/webpack.config.js +11 -0
@@ -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,9 @@ 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;
204
207
  }
205
208
  static subClass({ user, read_only, } = {}) {
206
209
  var _a;
@@ -242,6 +245,7 @@ class Table {
242
245
  const { fields, constraints, ...updDB } = upd_rec;
243
246
  if (updDB.ownership_field_id === "")
244
247
  delete updDB.ownership_field_id;
248
+ updDB.updated_at = new Date();
245
249
  await db_1.default.update("_sc_tables", updDB, tbl.id);
246
250
  //limited refresh if we do not have a client
247
251
  if (!db_1.default.getRequestContext()?.client)
@@ -255,6 +259,7 @@ class Table {
255
259
  if (!db_1.default.getRequestContext()?.client)
256
260
  await Table.state_refresh(true);
257
261
  };
262
+ //console.log({tbl});
258
263
  return t;
259
264
  }
260
265
  /**
@@ -324,7 +329,7 @@ class Table {
324
329
  const { getState } = require("../db/state");
325
330
  const provider = getState().table_providers[t.provider_name];
326
331
  if (provider)
327
- t.fields = await applyAsync(provider.fields, t.provider_cfg);
332
+ t.fields = await applyAsync(provider.fields, stringToJSON(t.provider_cfg));
328
333
  else
329
334
  t.fields = [];
330
335
  }
@@ -600,6 +605,7 @@ class Table {
600
605
  description: options.description || "",
601
606
  provider_name: options.provider_name,
602
607
  provider_cfg: options.provider_cfg,
608
+ updated_at: new Date(),
603
609
  };
604
610
  let pk_fld_id;
605
611
  if (!id) {
@@ -780,25 +786,50 @@ class Table {
780
786
  }
781
787
  }
782
788
  }
783
- async addDeleteSyncInfo(ids, timestamp) {
789
+ async addDeleteSyncInfo(ids, timestamp, ownerFieldName, ownershipFormula) {
784
790
  if (this.constructor.read_only)
785
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;
786
801
  await db_1.default.tryCatchInTransaction(async () => {
787
802
  if (ids.length > 0) {
788
803
  const schema = db_1.default.getTenantSchemaPrefix();
789
804
  const pkName = this.pk_name || "id";
790
805
  if (isNode()) {
791
- await db_1.default.query(`delete from ${schema}"${db_1.default.sqlsanitize(this.name)}_sync_info" where ref in (
792
- ${ids.map((row) => row[pkName]).join(",")})`);
793
- await db_1.default.query(`insert into ${schema}"${db_1.default.sqlsanitize(this.name)}_sync_info" (ref, last_modified, deleted)
794
- values ${ids
795
- .map((row) => `(${row[pkName]}, date_trunc('milliseconds', to_timestamp( ${timestamp.valueOf() / 1000.0} ) ), true)`)
796
- .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);
797
824
  }
798
825
  else {
826
+ const pkVals = ids.map((row) => String(row[pkName]));
827
+ const placeholders = pkVals
828
+ .map((_, i) => `$${i + 1}`)
829
+ .join(",");
799
830
  await db_1.default.query(`update "${db_1.default.sqlsanitize(this.name)}_sync_info"
800
831
  set deleted = true, modified_local = true
801
- where ref in (${ids.map((row) => row[pkName]).join(",")})`);
832
+ where ref in (${placeholders})`, pkVals);
802
833
  }
803
834
  }
804
835
  }, (e) => {
@@ -842,6 +873,12 @@ class Table {
842
873
  // get triggers on delete
843
874
  const triggers = await trigger_1.default.getTableTriggers("Delete", this);
844
875
  const fields = this.fields;
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
+ })();
845
882
  if (this.updateWhereWithOwnership(where, use_user)?.notAuthorized) {
846
883
  const state = require("../db/state").getState();
847
884
  state.log(4, `Not authorized to deleteRows in table ${this.name}.`);
@@ -854,6 +891,13 @@ class Table {
854
891
  expression: "__aggregation",
855
892
  attributes: { json: { table: this.name } },
856
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
+ }
857
901
  let rows;
858
902
  if (calc_agg_fields.length ||
859
903
  (use_user &&
@@ -863,6 +907,7 @@ class Table {
863
907
  where,
864
908
  forUser: use_user,
865
909
  forPublic: use_user?.role_id === 100,
910
+ joinFields: ownershipJoinFields,
866
911
  });
867
912
  }
868
913
  const deleteFileFields = fields.filter((f) => f.type === "File" && f.attributes?.also_delete_file);
@@ -900,32 +945,47 @@ class Table {
900
945
  }
901
946
  await db_1.default.tryCatchInTransaction(async () => {
902
947
  if (rows) {
903
- const delIds = rows.map((r) => r[this.pk_name]);
948
+ const pkIds = rows.map((r) => r[this.pk_name]);
904
949
  if (!db_1.default.isSQLite) {
905
950
  await db_1.default.deleteWhere(this.name, {
906
- [this.pk_name]: { in: delIds },
951
+ [this.pk_name]: { in: pkIds },
907
952
  });
908
953
  }
909
954
  else {
910
- 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(",")})`);
911
956
  }
912
957
  for (const row of rows)
913
958
  await this.auto_update_calc_aggregations(row);
914
959
  if (this.has_sync_info) {
915
960
  const dbTime = await db_1.default.time();
916
- await this.addDeleteSyncInfo(rows, dbTime);
961
+ await this.addDeleteSyncInfo(rows, dbTime, ownerFieldName, this.ownership_formula);
917
962
  }
918
963
  }
919
964
  else {
920
- const delIds = this.has_sync_info
921
- ? await db_1.default.select(this.name, where, {
922
- fields: [this.pk_name],
923
- })
924
- : null;
925
- await db_1.default.deleteWhere(this.name, where);
965
+ let preDeleteRows = null;
926
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) {
927
987
  const dbTime = await db_1.default.time();
928
- await this.addDeleteSyncInfo(delIds, dbTime);
988
+ await this.addDeleteSyncInfo(preDeleteRows, dbTime, ownerFieldName, this.ownership_formula);
929
989
  }
930
990
  }
931
991
  //if (fields.find((f) => f.primary_key)) await this.resetSequence();
@@ -961,7 +1021,7 @@ class Table {
961
1021
  if (this.fields) {
962
1022
  for (const f of this.fields) {
963
1023
  if (f.type && (0, common_types_1.instanceOfType)(f.type) && f.type.readFromDB)
964
- row[f.name] = f.type.readFromDB(row[f.name]);
1024
+ row[f.name] = f.type.readFromDB(row[f.name], f);
965
1025
  }
966
1026
  }
967
1027
  return row;
@@ -1124,9 +1184,20 @@ class Table {
1124
1184
  * @param fieldnm
1125
1185
  * @returns {Promise<Object[]>}
1126
1186
  */
1127
- async distinctValues(fieldnm, whereObj) {
1128
- if (whereObj) {
1129
- 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);
1130
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);
1131
1202
  return res.rows.map((r) => r[fieldnm]);
1132
1203
  }
@@ -1338,7 +1409,7 @@ class Table {
1338
1409
  joinFields,
1339
1410
  });
1340
1411
  }
1341
- 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);
1342
1413
  for (const f of fields)
1343
1414
  if (f.calculated && f.stored) {
1344
1415
  if (typeof f.type !== "string" &&
@@ -1480,7 +1551,8 @@ class Table {
1480
1551
  SELECT MAX(last_modified) "last_modified", ref
1481
1552
  FROM ${schema}"${db_1.default.sqlsanitize(this.name)}_sync_info"
1482
1553
  GROUP BY ref HAVING ref = ($1)`;
1483
- 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]);
1484
1556
  return result.rows;
1485
1557
  }, (e) => {
1486
1558
  require("../db/state")
@@ -1502,15 +1574,15 @@ class Table {
1502
1574
  }
1503
1575
  await db_1.default.query(`insert into ${schema}"${db_1.default.sqlsanitize(this.name)}_sync_info" (ref, last_modified, updated_fields)
1504
1576
  values(
1505
- $1,
1577
+ $1::text,
1506
1578
  date_trunc('milliseconds', to_timestamp($2)),
1507
1579
  $3::jsonb
1508
1580
  )`, [id, timestamp.valueOf() / 1000.0, JSON.stringify(fieldTimestamps)]);
1509
1581
  }
1510
1582
  else {
1511
1583
  await db_1.default.query(`insert into "${db_1.default.sqlsanitize(this.name)}_sync_info"
1512
- (ref, modified_local, deleted)
1513
- values('${id}', true, false)`);
1584
+ (ref, modified_local, deleted)
1585
+ values(CAST($1 AS TEXT), true, false)`, [id]);
1514
1586
  }
1515
1587
  }, (e) => {
1516
1588
  require("../db/state")
@@ -1531,13 +1603,13 @@ class Table {
1531
1603
  for (const k of Object.keys(v).filter((key) => key !== this.pk_name)) {
1532
1604
  fieldTimestamps[k] = timestamp;
1533
1605
  }
1534
- await db_1.default.query(`update ${schema}"${db_1.default.sqlsanitize(this.name)}_sync_info"
1535
- set
1606
+ await db_1.default.query(`update ${schema}"${db_1.default.sqlsanitize(this.name)}_sync_info"
1607
+ set
1536
1608
  last_modified=date_trunc('milliseconds', to_timestamp($1)),
1537
1609
  updated_fields =
1538
1610
  coalesce(updated_fields, '{}'::jsonb) || $4::jsonb
1539
- where
1540
- ref=$2 and last_modified = to_timestamp($3)`, [
1611
+ where
1612
+ ref=$2::text and last_modified = to_timestamp($3)`, [
1541
1613
  timestamp.valueOf() / 1000.0,
1542
1614
  id,
1543
1615
  oldLastModified.valueOf() / 1000.0,
@@ -1545,8 +1617,8 @@ class Table {
1545
1617
  ]);
1546
1618
  }
1547
1619
  else {
1548
- await db_1.default.query(`update "${db_1.default.sqlsanitize(this.name)}_sync_info" set modified_local = true
1549
- 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]);
1550
1622
  }
1551
1623
  }, (e) => {
1552
1624
  require("../db/state")
@@ -1758,11 +1830,37 @@ class Table {
1758
1830
  return valResCollector; //???
1759
1831
  if ("set_fields" in valResCollector)
1760
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
+ }
1761
1856
  if (Object.keys(joinFields).length > 0 ||
1762
1857
  fields.some((f) => f.expression === "__aggregation")) {
1763
1858
  state.log(6, `Inserting ${this.name} because join fields: ${JSON.stringify(v_in)}`);
1764
1859
  this.prepare_row_for_writing(v_in);
1765
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];
1766
1864
  let existing = await this.getJoinedRows({
1767
1865
  where: { [pk_name]: id },
1768
1866
  joinFields,
@@ -1775,7 +1873,7 @@ class Table {
1775
1873
  state.log(4, `Not authorized to insertRow in table ${this.name}. Inserted row not retrieved.`);
1776
1874
  return;
1777
1875
  }
1778
- 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);
1779
1877
  v = { ...v_in };
1780
1878
  for (const f of fields)
1781
1879
  if (f.calculated && f.stored)
@@ -1784,13 +1882,16 @@ class Table {
1784
1882
  await db_1.default.update(this.name, v, id, { pk_name, ...sqliteJsonCols });
1785
1883
  }
1786
1884
  else {
1787
- v = await apply_calculated_fields_stored(v_in, fields, this);
1885
+ v = await apply_calculated_fields_stored(v_in, fields, this, use_user);
1788
1886
  this.prepare_row_for_writing(v);
1789
1887
  state.log(6, `Inserting ${this.name} row: ${JSON.stringify(v)}`);
1790
1888
  id = await db_1.default.insert(this.name, v, {
1791
1889
  pk_name,
1792
1890
  ...sqliteJsonCols,
1793
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];
1794
1895
  }
1795
1896
  if (use_user &&
1796
1897
  use_user.role_id > this.min_role_write &&
@@ -1822,15 +1923,15 @@ class Table {
1822
1923
  if (isNode()) {
1823
1924
  // sync_info for insert
1824
1925
  const schemaPrefix = db_1.default.getTenantSchemaPrefix();
1926
+ const tsParam = (syncTimestamp ? syncTimestamp : await db_1.default.time()).valueOf() /
1927
+ 1000.0;
1825
1928
  await db_1.default.query(`insert into ${schemaPrefix}"${db_1.default.sqlsanitize(this.name)}_sync_info"
1826
- (ref, last_modified) values(
1827
- ${id}, date_trunc('milliseconds', to_timestamp(${(syncTimestamp ? syncTimestamp : await db_1.default.time()).valueOf() /
1828
- 1000.0})))`);
1929
+ (ref, last_modified) values($1::text, date_trunc('milliseconds', to_timestamp($2)))`, [id, tsParam]);
1829
1930
  }
1830
1931
  else {
1831
1932
  await db_1.default.query(`insert into "${db_1.default.sqlsanitize(this.name)}_sync_info"
1832
1933
  (last_modified, ref, modified_local, deleted)
1833
- values(NULL, ${id}, true, false)`);
1934
+ values(NULL, CAST($1 AS TEXT), true, false)`, [id]);
1834
1935
  }
1835
1936
  }, (e) => {
1836
1937
  state.log(2, `Error inserting sync info for table ${this.name}: ${e.message}`);
@@ -2142,10 +2243,12 @@ class Table {
2142
2243
  throw new Error("Unable to find a field with a primary key.");
2143
2244
  }
2144
2245
  await db_1.default.query(`create table ${schemaPrefix}"${(0, internal_1.sqlsanitize)(this.name)}_sync_info" (
2145
- ref integer,
2246
+ ref text not null,
2146
2247
  last_modified timestamp,
2147
2248
  deleted boolean default false,
2148
- updated_fields jsonb)`);
2249
+ updated_fields jsonb,
2250
+ owner_id integer,
2251
+ owner_fields jsonb)`);
2149
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)`);
2150
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)`);
2151
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)`);
@@ -2334,6 +2437,7 @@ class Table {
2334
2437
  throw new Error(`Unable to find table with id: ${this.id}`);
2335
2438
  }
2336
2439
  const { external, fields, constraints, ...upd_rec } = new_table_rec;
2440
+ upd_rec.updated_at = new Date();
2337
2441
  await db_1.default.update("_sc_tables", upd_rec, this.id);
2338
2442
  //limited refresh if we do not have a client
2339
2443
  if (!db_1.default.getRequestContext()?.client)