@carbonorm/carbonnode 4.0.0 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (158) hide show
  1. package/README.md +246 -507
  2. package/dist/api/executors/SqlExecutor.d.ts +6 -0
  3. package/dist/api/handlers/ExpressHandler.d.ts +2 -1
  4. package/dist/api/orm/builders/ConditionBuilder.d.ts +2 -0
  5. package/dist/api/types/ormInterfaces.d.ts +12 -0
  6. package/dist/api/utils/sqlAllowList.d.ts +2 -0
  7. package/dist/index.cjs.js +279 -20
  8. package/dist/index.cjs.js.map +1 -1
  9. package/dist/index.d.ts +1 -0
  10. package/dist/index.esm.js +278 -21
  11. package/dist/index.esm.js.map +1 -1
  12. package/package.json +1 -1
  13. package/scripts/assets/handlebars/C6.test.ts.handlebars +578 -32
  14. package/scripts/generateRestBindings.cjs +5 -5
  15. package/scripts/generateRestBindings.ts +5 -5
  16. package/src/__tests__/fixtures/createTestServer.ts +11 -3
  17. package/src/__tests__/fixtures/sqlResponses/actor.get.json +13 -0
  18. package/src/__tests__/fixtures/sqlResponses/sqlAllowList.blocked.json +3 -0
  19. package/src/__tests__/fixtures/sqlResponses/sqlAllowList.json +3 -0
  20. package/src/__tests__/sakila-db/C6.js +1 -1
  21. package/src/__tests__/sakila-db/C6.mysql.cnf +6 -0
  22. package/src/__tests__/sakila-db/C6.mysqldump.json +1 -0
  23. package/src/__tests__/sakila-db/C6.mysqldump.sql +720 -0
  24. package/src/__tests__/sakila-db/C6.sqlAllowList.json +94 -0
  25. package/src/__tests__/sakila-db/C6.test.ts +578 -32
  26. package/src/__tests__/sakila-db/C6.ts +1 -1
  27. package/src/__tests__/sakila-db/sqlResponses/C6.actor.delete.json +10 -0
  28. package/src/__tests__/sakila-db/sqlResponses/C6.actor.delete.lookup.json +9 -0
  29. package/src/__tests__/sakila-db/sqlResponses/C6.actor.get.json +14 -0
  30. package/src/__tests__/sakila-db/sqlResponses/C6.actor.join.json +15 -0
  31. package/src/__tests__/sakila-db/sqlResponses/C6.actor.post.json +12 -0
  32. package/src/__tests__/sakila-db/sqlResponses/C6.actor.post.latest.json +14 -0
  33. package/src/__tests__/sakila-db/sqlResponses/C6.actor.put.json +11 -0
  34. package/src/__tests__/sakila-db/sqlResponses/C6.actor.put.lookup.json +16 -0
  35. package/src/__tests__/sakila-db/sqlResponses/C6.actor.seed.json +14 -0
  36. package/src/__tests__/sakila-db/sqlResponses/C6.address.delete.json +10 -0
  37. package/src/__tests__/sakila-db/sqlResponses/C6.address.delete.lookup.json +9 -0
  38. package/src/__tests__/sakila-db/sqlResponses/C6.address.fk.current.json +358 -0
  39. package/src/__tests__/sakila-db/sqlResponses/C6.address.fk.referenced.json +158 -0
  40. package/src/__tests__/sakila-db/sqlResponses/C6.address.get.json +22 -0
  41. package/src/__tests__/sakila-db/sqlResponses/C6.address.join.json +24 -0
  42. package/src/__tests__/sakila-db/sqlResponses/C6.address.post.json +16 -0
  43. package/src/__tests__/sakila-db/sqlResponses/C6.address.post.latest.json +22 -0
  44. package/src/__tests__/sakila-db/sqlResponses/C6.address.put.json +11 -0
  45. package/src/__tests__/sakila-db/sqlResponses/C6.address.put.lookup.json +24 -0
  46. package/src/__tests__/sakila-db/sqlResponses/C6.address.seed.json +22 -0
  47. package/src/__tests__/sakila-db/sqlResponses/C6.category.delete.json +10 -0
  48. package/src/__tests__/sakila-db/sqlResponses/C6.category.delete.lookup.json +9 -0
  49. package/src/__tests__/sakila-db/sqlResponses/C6.category.get.json +13 -0
  50. package/src/__tests__/sakila-db/sqlResponses/C6.category.join.json +14 -0
  51. package/src/__tests__/sakila-db/sqlResponses/C6.category.post.json +11 -0
  52. package/src/__tests__/sakila-db/sqlResponses/C6.category.post.latest.json +13 -0
  53. package/src/__tests__/sakila-db/sqlResponses/C6.category.put.json +11 -0
  54. package/src/__tests__/sakila-db/sqlResponses/C6.category.put.lookup.json +15 -0
  55. package/src/__tests__/sakila-db/sqlResponses/C6.category.seed.json +13 -0
  56. package/src/__tests__/sakila-db/sqlResponses/C6.city.delete.json +10 -0
  57. package/src/__tests__/sakila-db/sqlResponses/C6.city.delete.lookup.json +9 -0
  58. package/src/__tests__/sakila-db/sqlResponses/C6.city.fk.current.json +158 -0
  59. package/src/__tests__/sakila-db/sqlResponses/C6.city.fk.referenced.json +133 -0
  60. package/src/__tests__/sakila-db/sqlResponses/C6.city.get.json +14 -0
  61. package/src/__tests__/sakila-db/sqlResponses/C6.city.join.json +15 -0
  62. package/src/__tests__/sakila-db/sqlResponses/C6.city.post.json +12 -0
  63. package/src/__tests__/sakila-db/sqlResponses/C6.city.post.latest.json +14 -0
  64. package/src/__tests__/sakila-db/sqlResponses/C6.city.put.json +11 -0
  65. package/src/__tests__/sakila-db/sqlResponses/C6.city.put.lookup.json +16 -0
  66. package/src/__tests__/sakila-db/sqlResponses/C6.city.seed.json +14 -0
  67. package/src/__tests__/sakila-db/sqlResponses/C6.country.delete.json +10 -0
  68. package/src/__tests__/sakila-db/sqlResponses/C6.country.delete.lookup.json +9 -0
  69. package/src/__tests__/sakila-db/sqlResponses/C6.country.get.json +13 -0
  70. package/src/__tests__/sakila-db/sqlResponses/C6.country.join.json +15 -0
  71. package/src/__tests__/sakila-db/sqlResponses/C6.country.post.json +11 -0
  72. package/src/__tests__/sakila-db/sqlResponses/C6.country.post.latest.json +13 -0
  73. package/src/__tests__/sakila-db/sqlResponses/C6.country.put.json +11 -0
  74. package/src/__tests__/sakila-db/sqlResponses/C6.country.put.lookup.json +15 -0
  75. package/src/__tests__/sakila-db/sqlResponses/C6.country.seed.json +13 -0
  76. package/src/__tests__/sakila-db/sqlResponses/C6.customer.delete.json +10 -0
  77. package/src/__tests__/sakila-db/sqlResponses/C6.customer.delete.lookup.json +9 -0
  78. package/src/__tests__/sakila-db/sqlResponses/C6.customer.fk.current.json +283 -0
  79. package/src/__tests__/sakila-db/sqlResponses/C6.customer.fk.referenced.json +358 -0
  80. package/src/__tests__/sakila-db/sqlResponses/C6.customer.get.json +19 -0
  81. package/src/__tests__/sakila-db/sqlResponses/C6.customer.join.json +29 -0
  82. package/src/__tests__/sakila-db/sqlResponses/C6.customer.post.json +17 -0
  83. package/src/__tests__/sakila-db/sqlResponses/C6.customer.post.latest.json +19 -0
  84. package/src/__tests__/sakila-db/sqlResponses/C6.customer.put.json +11 -0
  85. package/src/__tests__/sakila-db/sqlResponses/C6.customer.put.lookup.json +21 -0
  86. package/src/__tests__/sakila-db/sqlResponses/C6.customer.seed.json +19 -0
  87. package/src/__tests__/sakila-db/sqlResponses/C6.film.delete.json +10 -0
  88. package/src/__tests__/sakila-db/sqlResponses/C6.film.delete.lookup.json +9 -0
  89. package/src/__tests__/sakila-db/sqlResponses/C6.film.fk.current.json +383 -0
  90. package/src/__tests__/sakila-db/sqlResponses/C6.film.fk.referenced.json +38 -0
  91. package/src/__tests__/sakila-db/sqlResponses/C6.film.get.json +23 -0
  92. package/src/__tests__/sakila-db/sqlResponses/C6.film.join.json +24 -0
  93. package/src/__tests__/sakila-db/sqlResponses/C6.film.post.json +20 -0
  94. package/src/__tests__/sakila-db/sqlResponses/C6.film.post.latest.json +23 -0
  95. package/src/__tests__/sakila-db/sqlResponses/C6.film.put.json +11 -0
  96. package/src/__tests__/sakila-db/sqlResponses/C6.film.put.lookup.json +25 -0
  97. package/src/__tests__/sakila-db/sqlResponses/C6.film.seed.json +23 -0
  98. package/src/__tests__/sakila-db/sqlResponses/C6.inventory.delete.json +10 -0
  99. package/src/__tests__/sakila-db/sqlResponses/C6.inventory.delete.lookup.json +9 -0
  100. package/src/__tests__/sakila-db/sqlResponses/C6.inventory.fk.current.json +158 -0
  101. package/src/__tests__/sakila-db/sqlResponses/C6.inventory.fk.referenced.json +20 -0
  102. package/src/__tests__/sakila-db/sqlResponses/C6.inventory.get.json +14 -0
  103. package/src/__tests__/sakila-db/sqlResponses/C6.inventory.join.json +25 -0
  104. package/src/__tests__/sakila-db/sqlResponses/C6.inventory.post.json +12 -0
  105. package/src/__tests__/sakila-db/sqlResponses/C6.inventory.post.latest.json +14 -0
  106. package/src/__tests__/sakila-db/sqlResponses/C6.inventory.put.json +11 -0
  107. package/src/__tests__/sakila-db/sqlResponses/C6.inventory.put.lookup.json +16 -0
  108. package/src/__tests__/sakila-db/sqlResponses/C6.inventory.seed.json +14 -0
  109. package/src/__tests__/sakila-db/sqlResponses/C6.language.delete.json +10 -0
  110. package/src/__tests__/sakila-db/sqlResponses/C6.language.delete.lookup.json +9 -0
  111. package/src/__tests__/sakila-db/sqlResponses/C6.language.get.json +13 -0
  112. package/src/__tests__/sakila-db/sqlResponses/C6.language.join.json +24 -0
  113. package/src/__tests__/sakila-db/sqlResponses/C6.language.post.json +11 -0
  114. package/src/__tests__/sakila-db/sqlResponses/C6.language.post.latest.json +13 -0
  115. package/src/__tests__/sakila-db/sqlResponses/C6.language.put.json +11 -0
  116. package/src/__tests__/sakila-db/sqlResponses/C6.language.put.lookup.json +15 -0
  117. package/src/__tests__/sakila-db/sqlResponses/C6.language.seed.json +13 -0
  118. package/src/__tests__/sakila-db/sqlResponses/C6.payment.delete.json +10 -0
  119. package/src/__tests__/sakila-db/sqlResponses/C6.payment.delete.lookup.json +9 -0
  120. package/src/__tests__/sakila-db/sqlResponses/C6.payment.fk.current.json +233 -0
  121. package/src/__tests__/sakila-db/sqlResponses/C6.payment.fk.referenced.json +233 -0
  122. package/src/__tests__/sakila-db/sqlResponses/C6.payment.get.json +17 -0
  123. package/src/__tests__/sakila-db/sqlResponses/C6.payment.join.json +24 -0
  124. package/src/__tests__/sakila-db/sqlResponses/C6.payment.post.json +15 -0
  125. package/src/__tests__/sakila-db/sqlResponses/C6.payment.post.latest.json +17 -0
  126. package/src/__tests__/sakila-db/sqlResponses/C6.payment.put.json +11 -0
  127. package/src/__tests__/sakila-db/sqlResponses/C6.payment.put.lookup.json +19 -0
  128. package/src/__tests__/sakila-db/sqlResponses/C6.payment.seed.json +17 -0
  129. package/src/__tests__/sakila-db/sqlResponses/C6.rental.delete.json +10 -0
  130. package/src/__tests__/sakila-db/sqlResponses/C6.rental.delete.lookup.json +9 -0
  131. package/src/__tests__/sakila-db/sqlResponses/C6.rental.fk.current.json +233 -0
  132. package/src/__tests__/sakila-db/sqlResponses/C6.rental.fk.referenced.json +34 -0
  133. package/src/__tests__/sakila-db/sqlResponses/C6.rental.get.json +17 -0
  134. package/src/__tests__/sakila-db/sqlResponses/C6.rental.join.json +24 -0
  135. package/src/__tests__/sakila-db/sqlResponses/C6.rental.post.json +15 -0
  136. package/src/__tests__/sakila-db/sqlResponses/C6.rental.post.latest.json +17 -0
  137. package/src/__tests__/sakila-db/sqlResponses/C6.rental.put.json +11 -0
  138. package/src/__tests__/sakila-db/sqlResponses/C6.rental.put.lookup.json +19 -0
  139. package/src/__tests__/sakila-db/sqlResponses/C6.rental.seed.json +17 -0
  140. package/src/__tests__/sakila-db/sqlResponses/C6.staff.fk.current.json +34 -0
  141. package/src/__tests__/sakila-db/sqlResponses/C6.staff.fk.referenced.json +20 -0
  142. package/src/__tests__/sakila-db/sqlResponses/C6.staff.get.json +21 -0
  143. package/src/__tests__/sakila-db/sqlResponses/C6.staff.join.json +31 -0
  144. package/src/__tests__/sakila-db/sqlResponses/C6.staff.seed.json +21 -0
  145. package/src/__tests__/sakila-db/sqlResponses/C6.store.fk.current.json +20 -0
  146. package/src/__tests__/sakila-db/sqlResponses/C6.store.fk.referenced.json +34 -0
  147. package/src/__tests__/sakila-db/sqlResponses/C6.store.get.json +14 -0
  148. package/src/__tests__/sakila-db/sqlResponses/C6.store.join.json +24 -0
  149. package/src/__tests__/sakila-db/sqlResponses/C6.store.seed.json +14 -0
  150. package/src/__tests__/sakila.generated.test.ts +31 -0
  151. package/src/__tests__/sqlAllowList.test.ts +135 -0
  152. package/src/__tests__/sqlBuilders.test.ts +17 -0
  153. package/src/api/executors/SqlExecutor.ts +156 -0
  154. package/src/api/handlers/ExpressHandler.ts +10 -1
  155. package/src/api/orm/builders/ConditionBuilder.ts +27 -7
  156. package/src/api/types/ormInterfaces.ts +15 -0
  157. package/src/api/utils/sqlAllowList.ts +54 -0
  158. package/src/index.ts +1 -0
package/dist/index.d.ts CHANGED
@@ -36,6 +36,7 @@ export * from "./api/utils/determineRuntimeJsType";
36
36
  export * from "./api/utils/logger";
37
37
  export * from "./api/utils/normalizeSingularRequest";
38
38
  export * from "./api/utils/sortAndSerializeQueryObject";
39
+ export * from "./api/utils/sqlAllowList";
39
40
  export * from "./api/utils/testHelpers";
40
41
  export * from "./api/utils/toastNotifier";
41
42
  export * from "./variables/getEnvVar";
package/dist/index.esm.js CHANGED
@@ -1824,8 +1824,9 @@ var ConditionBuilder = /** @class */ (function (_super) {
1824
1824
  }
1825
1825
  throw new Error('Unsupported operand type in SQL expression.');
1826
1826
  };
1827
- ConditionBuilder.prototype.isPlainArrayLiteral = function (value) {
1827
+ ConditionBuilder.prototype.isPlainArrayLiteral = function (value, allowColumnRefs) {
1828
1828
  var _this = this;
1829
+ if (allowColumnRefs === void 0) { allowColumnRefs = false; }
1829
1830
  if (!Array.isArray(value))
1830
1831
  return false;
1831
1832
  return value.every(function (item) {
@@ -1835,14 +1836,15 @@ var ConditionBuilder = /** @class */ (function (_super) {
1835
1836
  if (type === 'string' || type === 'number' || type === 'boolean')
1836
1837
  return true;
1837
1838
  if (Array.isArray(item))
1838
- return _this.isPlainArrayLiteral(item);
1839
+ return _this.isPlainArrayLiteral(item, allowColumnRefs);
1839
1840
  if (item && typeof item === 'object')
1840
- return _this.isPlainObjectLiteral(item);
1841
+ return _this.isPlainObjectLiteral(item, allowColumnRefs);
1841
1842
  return false;
1842
1843
  });
1843
1844
  };
1844
- ConditionBuilder.prototype.isPlainObjectLiteral = function (value) {
1845
+ ConditionBuilder.prototype.isPlainObjectLiteral = function (value, allowColumnRefs) {
1845
1846
  var _this = this;
1847
+ if (allowColumnRefs === void 0) { allowColumnRefs = false; }
1846
1848
  if (!value || typeof value !== 'object' || Array.isArray(value))
1847
1849
  return false;
1848
1850
  if (value instanceof Date)
@@ -1861,17 +1863,37 @@ var ConditionBuilder = /** @class */ (function (_super) {
1861
1863
  })) {
1862
1864
  return false;
1863
1865
  }
1864
- if (entries.some(function (_a) {
1865
- var key = _a[0];
1866
- return typeof key === 'string' && (_this.isColumnRef(key) || key.includes('.'));
1867
- })) {
1868
- return false;
1866
+ if (!allowColumnRefs) {
1867
+ if (entries.some(function (_a) {
1868
+ var key = _a[0];
1869
+ return typeof key === 'string' && (_this.isColumnRef(key) || key.includes('.'));
1870
+ })) {
1871
+ return false;
1872
+ }
1869
1873
  }
1870
1874
  return true;
1871
1875
  };
1876
+ ConditionBuilder.prototype.resolveColumnDefinition = function (column) {
1877
+ var _a, _b, _c, _d;
1878
+ if (!column || typeof column !== 'string' || !column.includes('.'))
1879
+ return undefined;
1880
+ var _e = column.split('.', 2), prefix = _e[0], colName = _e[1];
1881
+ var tableName = (_a = this.aliasMap[prefix]) !== null && _a !== void 0 ? _a : prefix;
1882
+ var table = (_c = (_b = this.config.C6) === null || _b === void 0 ? void 0 : _b.TABLES) === null || _c === void 0 ? void 0 : _c[tableName];
1883
+ if (!(table === null || table === void 0 ? void 0 : table.TYPE_VALIDATION))
1884
+ return undefined;
1885
+ return (_d = table.TYPE_VALIDATION[colName]) !== null && _d !== void 0 ? _d : table.TYPE_VALIDATION["".concat(tableName, ".").concat(colName)];
1886
+ };
1887
+ ConditionBuilder.prototype.isJsonColumn = function (column) {
1888
+ var columnDef = this.resolveColumnDefinition(column);
1889
+ var mysqlType = columnDef === null || columnDef === void 0 ? void 0 : columnDef.MYSQL_TYPE;
1890
+ return typeof mysqlType === 'string' && mysqlType.toLowerCase().includes('json');
1891
+ };
1872
1892
  ConditionBuilder.prototype.serializeUpdateValue = function (value, params, contextColumn) {
1873
1893
  var normalized = value instanceof Map ? Object.fromEntries(value) : value;
1874
- if (this.isPlainArrayLiteral(normalized) || this.isPlainObjectLiteral(normalized)) {
1894
+ var allowColumnRefs = this.isJsonColumn(contextColumn);
1895
+ if (this.isPlainArrayLiteral(normalized, allowColumnRefs)
1896
+ || this.isPlainObjectLiteral(normalized, allowColumnRefs)) {
1875
1897
  return this.addParam(params, contextColumn !== null && contextColumn !== void 0 ? contextColumn : '', JSON.stringify(normalized));
1876
1898
  }
1877
1899
  var _a = this.serializeOperand(normalized, params, contextColumn), sql = _a.sql, isReference = _a.isReference, isExpression = _a.isExpression, isSubSelect = _a.isSubSelect;
@@ -2938,6 +2960,63 @@ function normalizeSingularRequest(requestMethod, request, restModel, removedPrim
2938
2960
  return __assign(__assign({}, normalized), { dataInsertMultipleRows: dataInsertMultipleRows, cacheResults: cacheResults, fetchDependencies: fetchDependencies, debug: debug, success: success, error: error });
2939
2961
  }
2940
2962
 
2963
+ var allowListCache = new Map();
2964
+ var normalizeSql = function (sql) {
2965
+ return sql.replace(/\s+/g, " ").trim();
2966
+ };
2967
+ var parseAllowList = function (raw, sourcePath) {
2968
+ var parsed;
2969
+ try {
2970
+ parsed = JSON.parse(raw);
2971
+ }
2972
+ catch (error) {
2973
+ throw new Error("SQL allowlist at ".concat(sourcePath, " is not valid JSON."));
2974
+ }
2975
+ if (!Array.isArray(parsed)) {
2976
+ throw new Error("SQL allowlist at ".concat(sourcePath, " must be a JSON array of strings."));
2977
+ }
2978
+ var sqlEntries = parsed
2979
+ .filter(function (entry) { return typeof entry === "string"; })
2980
+ .map(normalizeSql)
2981
+ .filter(function (entry) { return entry.length > 0; });
2982
+ if (sqlEntries.length !== parsed.length) {
2983
+ throw new Error("SQL allowlist at ".concat(sourcePath, " must contain only string entries."));
2984
+ }
2985
+ return sqlEntries;
2986
+ };
2987
+ var loadSqlAllowList = function (allowListPath) { return __awaiter(void 0, void 0, void 0, function () {
2988
+ var readFile, raw, sqlEntries, allowList;
2989
+ return __generator(this, function (_a) {
2990
+ switch (_a.label) {
2991
+ case 0:
2992
+ if (allowListCache.has(allowListPath)) {
2993
+ return [2 /*return*/, allowListCache.get(allowListPath)];
2994
+ }
2995
+ if (!isNode()) {
2996
+ throw new Error("SQL allowlist validation requires a Node runtime.");
2997
+ }
2998
+ return [4 /*yield*/, import('node:fs/promises')];
2999
+ case 1:
3000
+ readFile = (_a.sent()).readFile;
3001
+ _a.label = 2;
3002
+ case 2:
3003
+ _a.trys.push([2, 4, , 5]);
3004
+ return [4 /*yield*/, readFile(allowListPath, "utf-8")];
3005
+ case 3:
3006
+ raw = _a.sent();
3007
+ return [3 /*break*/, 5];
3008
+ case 4:
3009
+ _a.sent();
3010
+ throw new Error("SQL allowlist file not found at ".concat(allowListPath, "."));
3011
+ case 5:
3012
+ sqlEntries = parseAllowList(raw, allowListPath);
3013
+ allowList = new Set(sqlEntries);
3014
+ allowListCache.set(allowListPath, allowList);
3015
+ return [2 /*return*/, allowList];
3016
+ }
3017
+ });
3018
+ }); };
3019
+
2941
3020
  var SqlExecutor = /** @class */ (function (_super) {
2942
3021
  __extends(SqlExecutor, _super);
2943
3022
  function SqlExecutor() {
@@ -2970,10 +3049,10 @@ var SqlExecutor = /** @class */ (function (_super) {
2970
3049
  switch (_a) {
2971
3050
  case 'GET': return [3 /*break*/, 1];
2972
3051
  case 'POST': return [3 /*break*/, 3];
2973
- case 'PUT': return [3 /*break*/, 5];
2974
- case 'DELETE': return [3 /*break*/, 7];
3052
+ case 'PUT': return [3 /*break*/, 6];
3053
+ case 'DELETE': return [3 /*break*/, 9];
2975
3054
  }
2976
- return [3 /*break*/, 9];
3055
+ return [3 /*break*/, 12];
2977
3056
  case 1: return [4 /*yield*/, this.runQuery()];
2978
3057
  case 2:
2979
3058
  rest = _b.sent();
@@ -2981,16 +3060,25 @@ var SqlExecutor = /** @class */ (function (_super) {
2981
3060
  case 3: return [4 /*yield*/, this.runQuery()];
2982
3061
  case 4:
2983
3062
  result = _b.sent();
3063
+ return [4 /*yield*/, this.broadcastWebsocketIfConfigured()];
3064
+ case 5:
3065
+ _b.sent();
2984
3066
  return [2 /*return*/, result];
2985
- case 5: return [4 /*yield*/, this.runQuery()];
2986
- case 6:
3067
+ case 6: return [4 /*yield*/, this.runQuery()];
3068
+ case 7:
2987
3069
  result = _b.sent();
2988
- return [2 /*return*/, result];
2989
- case 7: return [4 /*yield*/, this.runQuery()];
3070
+ return [4 /*yield*/, this.broadcastWebsocketIfConfigured()];
2990
3071
  case 8:
3072
+ _b.sent();
3073
+ return [2 /*return*/, result];
3074
+ case 9: return [4 /*yield*/, this.runQuery()];
3075
+ case 10:
2991
3076
  result = _b.sent();
3077
+ return [4 /*yield*/, this.broadcastWebsocketIfConfigured()];
3078
+ case 11:
3079
+ _b.sent();
2992
3080
  return [2 /*return*/, result];
2993
- case 9: throw new Error("Unsupported request method: ".concat(method));
3081
+ case 12: throw new Error("Unsupported request method: ".concat(method));
2994
3082
  }
2995
3083
  });
2996
3084
  });
@@ -3051,6 +3139,149 @@ var SqlExecutor = /** @class */ (function (_super) {
3051
3139
  return "'".concat(val.toISOString().slice(0, 19).replace('T', ' '), "'");
3052
3140
  return "'".concat(JSON.stringify(val), "'");
3053
3141
  };
3142
+ SqlExecutor.prototype.stripRequestMetadata = function (source) {
3143
+ var ignoredKeys = new Set([
3144
+ C6Constants.SELECT,
3145
+ C6Constants.UPDATE,
3146
+ C6Constants.DELETE,
3147
+ C6Constants.WHERE,
3148
+ C6Constants.JOIN,
3149
+ C6Constants.PAGINATION,
3150
+ C6Constants.INSERT,
3151
+ C6Constants.REPLACE,
3152
+ "dataInsertMultipleRows",
3153
+ "cacheResults",
3154
+ "fetchDependencies",
3155
+ "debug",
3156
+ "success",
3157
+ "error",
3158
+ ]);
3159
+ var filtered = {};
3160
+ for (var _i = 0, _a = Object.entries(source); _i < _a.length; _i++) {
3161
+ var _b = _a[_i], key = _b[0], value = _b[1];
3162
+ if (!ignoredKeys.has(key)) {
3163
+ filtered[key] = value;
3164
+ }
3165
+ }
3166
+ return filtered;
3167
+ };
3168
+ SqlExecutor.prototype.normalizeRequestPayload = function (source) {
3169
+ var _a;
3170
+ var columns = this.config.restModel.COLUMNS;
3171
+ var validColumns = new Set(Object.values(columns));
3172
+ var normalized = {};
3173
+ for (var _i = 0, _b = Object.entries(source); _i < _b.length; _i++) {
3174
+ var _c = _b[_i], key = _c[0], value = _c[1];
3175
+ var shortKey = (_a = columns[key]) !== null && _a !== void 0 ? _a : (key.includes(".") ? key.split(".").pop() : key);
3176
+ if (validColumns.has(shortKey)) {
3177
+ normalized[shortKey] = value;
3178
+ }
3179
+ }
3180
+ return normalized;
3181
+ };
3182
+ SqlExecutor.prototype.extractRequestBody = function () {
3183
+ var _a, _b;
3184
+ var request = this.request;
3185
+ if (this.config.requestMethod === C6Constants.POST) {
3186
+ if (Array.isArray(request.dataInsertMultipleRows) && request.dataInsertMultipleRows.length > 0) {
3187
+ return request.dataInsertMultipleRows[0];
3188
+ }
3189
+ if (C6Constants.INSERT in request) {
3190
+ return (_a = request[C6Constants.INSERT]) !== null && _a !== void 0 ? _a : {};
3191
+ }
3192
+ if (C6Constants.REPLACE in request) {
3193
+ return (_b = request[C6Constants.REPLACE]) !== null && _b !== void 0 ? _b : {};
3194
+ }
3195
+ return this.stripRequestMetadata(request);
3196
+ }
3197
+ if (this.config.requestMethod === C6Constants.PUT) {
3198
+ if (request[C6Constants.UPDATE] && typeof request[C6Constants.UPDATE] === "object") {
3199
+ return request[C6Constants.UPDATE];
3200
+ }
3201
+ return this.stripRequestMetadata(request);
3202
+ }
3203
+ return {};
3204
+ };
3205
+ SqlExecutor.prototype.extractPrimaryKeyValues = function () {
3206
+ var _a, _b, _c;
3207
+ var request = this.request;
3208
+ var where = request === null || request === void 0 ? void 0 : request[C6Constants.WHERE];
3209
+ var sources = [request, (where && typeof where === "object" && !Array.isArray(where)) ? where : undefined];
3210
+ var columns = this.config.restModel.COLUMNS;
3211
+ var primaryShorts = (_a = this.config.restModel.PRIMARY_SHORT) !== null && _a !== void 0 ? _a : [];
3212
+ var primaryFulls = (_b = this.config.restModel.PRIMARY) !== null && _b !== void 0 ? _b : [];
3213
+ var pkValues = {};
3214
+ var _loop_1 = function (pkShort) {
3215
+ var value = undefined;
3216
+ for (var _d = 0, sources_1 = sources; _d < sources_1.length; _d++) {
3217
+ var source = sources_1[_d];
3218
+ if (source && pkShort in source) {
3219
+ value = source[pkShort];
3220
+ break;
3221
+ }
3222
+ }
3223
+ if (value === undefined) {
3224
+ var fullKey = (_c = primaryFulls.find(function (key) { return key.endsWith("." + pkShort); })) !== null && _c !== void 0 ? _c : Object.keys(columns).find(function (key) { return columns[key] === pkShort; });
3225
+ if (fullKey) {
3226
+ for (var _e = 0, sources_2 = sources; _e < sources_2.length; _e++) {
3227
+ var source = sources_2[_e];
3228
+ if (source && fullKey in source) {
3229
+ value = source[fullKey];
3230
+ break;
3231
+ }
3232
+ }
3233
+ }
3234
+ }
3235
+ if (value !== undefined) {
3236
+ pkValues[pkShort] = value;
3237
+ }
3238
+ };
3239
+ for (var _i = 0, primaryShorts_1 = primaryShorts; _i < primaryShorts_1.length; _i++) {
3240
+ var pkShort = primaryShorts_1[_i];
3241
+ _loop_1(pkShort);
3242
+ }
3243
+ if (primaryShorts.length > 0 && Object.keys(pkValues).length < primaryShorts.length) {
3244
+ return null;
3245
+ }
3246
+ return Object.keys(pkValues).length > 0 ? pkValues : null;
3247
+ };
3248
+ SqlExecutor.prototype.broadcastWebsocketIfConfigured = function () {
3249
+ return __awaiter(this, void 0, void 0, function () {
3250
+ var broadcast, payload, error_1;
3251
+ var _a, _b;
3252
+ return __generator(this, function (_c) {
3253
+ switch (_c.label) {
3254
+ case 0:
3255
+ broadcast = this.config.websocketBroadcast;
3256
+ if (!broadcast || this.config.requestMethod === C6Constants.GET)
3257
+ return [2 /*return*/];
3258
+ payload = {
3259
+ REST: {
3260
+ TABLE_NAME: this.config.restModel.TABLE_NAME,
3261
+ TABLE_PREFIX: (_b = (_a = this.config.C6) === null || _a === void 0 ? void 0 : _a.PREFIX) !== null && _b !== void 0 ? _b : "",
3262
+ METHOD: this.config.requestMethod,
3263
+ REQUEST: this.normalizeRequestPayload(this.extractRequestBody()),
3264
+ REQUEST_PRIMARY_KEY: this.extractPrimaryKeyValues(),
3265
+ },
3266
+ };
3267
+ _c.label = 1;
3268
+ case 1:
3269
+ _c.trys.push([1, 3, , 4]);
3270
+ return [4 /*yield*/, broadcast(payload)];
3271
+ case 2:
3272
+ _c.sent();
3273
+ return [3 /*break*/, 4];
3274
+ case 3:
3275
+ error_1 = _c.sent();
3276
+ if (this.config.verbose) {
3277
+ console.error("[SQL EXECUTOR] websocketBroadcast failed", error_1);
3278
+ }
3279
+ return [3 /*break*/, 4];
3280
+ case 4: return [2 /*return*/];
3281
+ }
3282
+ });
3283
+ });
3284
+ };
3054
3285
  SqlExecutor.prototype.runQuery = function () {
3055
3286
  return __awaiter(this, void 0, void 0, function () {
3056
3287
  var TABLE_NAME, method, builder, QueryResult, formatted, toUnnamed, _a, sql, values;
@@ -3082,6 +3313,9 @@ var SqlExecutor = /** @class */ (function (_super) {
3082
3313
  this.config.verbose && console.log("[SQL EXECUTOR] \uD83E\uDDE0 Formatted ".concat(method.toUpperCase(), " SQL:"), formatted);
3083
3314
  toUnnamed = namedPlaceholders();
3084
3315
  _a = toUnnamed(QueryResult.sql, QueryResult.params), sql = _a[0], values = _a[1];
3316
+ return [4 /*yield*/, this.validateSqlAllowList(sql)];
3317
+ case 1:
3318
+ _b.sent();
3085
3319
  return [4 /*yield*/, this.withConnection(function (conn) { return __awaiter(_this, void 0, void 0, function () {
3086
3320
  var result;
3087
3321
  return __generator(this, function (_a) {
@@ -3106,7 +3340,29 @@ var SqlExecutor = /** @class */ (function (_super) {
3106
3340
  }
3107
3341
  });
3108
3342
  }); })];
3109
- case 1: return [2 /*return*/, _b.sent()];
3343
+ case 2: return [2 /*return*/, _b.sent()];
3344
+ }
3345
+ });
3346
+ });
3347
+ };
3348
+ SqlExecutor.prototype.validateSqlAllowList = function (sql) {
3349
+ return __awaiter(this, void 0, void 0, function () {
3350
+ var allowListPath, allowList, normalized;
3351
+ return __generator(this, function (_a) {
3352
+ switch (_a.label) {
3353
+ case 0:
3354
+ allowListPath = this.config.sqlAllowListPath;
3355
+ if (!allowListPath) {
3356
+ return [2 /*return*/];
3357
+ }
3358
+ return [4 /*yield*/, loadSqlAllowList(allowListPath)];
3359
+ case 1:
3360
+ allowList = _a.sent();
3361
+ normalized = normalizeSql(sql);
3362
+ if (!allowList.has(normalized)) {
3363
+ throw new Error("SQL statement is not permitted by allowlist (".concat(allowListPath, ")."));
3364
+ }
3365
+ return [2 /*return*/];
3110
3366
  }
3111
3367
  });
3112
3368
  });
@@ -3123,7 +3379,7 @@ var SqlExecutor$1 = /*#__PURE__*/Object.freeze({
3123
3379
  // note sure how it would help anyone actually...
3124
3380
  function ExpressHandler(_a) {
3125
3381
  var _this = this;
3126
- var C6 = _a.C6, mysqlPool = _a.mysqlPool;
3382
+ var C6 = _a.C6, mysqlPool = _a.mysqlPool, sqlAllowListPath = _a.sqlAllowListPath;
3127
3383
  return function (req, res, next) { return __awaiter(_this, void 0, void 0, function () {
3128
3384
  var incomingMethod, table, primary, methodOverrideRaw, methodOverride, treatAsGet, method, payload, restModel, primaryKeys_1, primaryShortKeys_1, columnMap_1, resolveShortKey_1, hasPrimaryKeyValues, primaryKeyName, response, err_1;
3129
3385
  var _a, _b, _c, _d, _e, _f, _g;
@@ -3209,6 +3465,7 @@ function ExpressHandler(_a) {
3209
3465
  return [4 /*yield*/, restRequest({
3210
3466
  C6: C6,
3211
3467
  mysqlPool: mysqlPool,
3468
+ sqlAllowListPath: sqlAllowListPath,
3212
3469
  requestMethod: method,
3213
3470
  restModel: C6.TABLES[table]
3214
3471
  })(payload)];
@@ -3327,5 +3584,5 @@ function isVerbose () {
3327
3584
  return ['true', '1', 'yes', 'on'].includes(envVerbose.toLowerCase());
3328
3585
  }
3329
3586
 
3330
- export { A, AggregateBuilder, C6C, C6Constants, ConditionBuilder, DELETE, DeleteQueryBuilder, Executor, ExpressHandler, F, GET, HttpExecutor, JoinBuilder, POST, PUT, PaginationBuilder, PostQueryBuilder, SelectQueryBuilder, SqlExecutor, TestRestfulResponse, UpdateQueryBuilder, apiRequestCache, axiosInstance, bbox, carbonNodeQsStringify, checkAllRequestsComplete, checkCache, clearCache, convertForRequestBody, convertHexIfBinary, derivedTable, determineRuntimeJsType, distSphere, eFetchDependencies, error, fieldEq, getEnvVar, getPrimaryKeyTypes, group, info, isDerivedTableKey, isLocal, isNode, isTest, isVerbose, normalizeSingularRequest, onError, onSuccess, removeInvalidKeys, removePrefixIfExists, resolveDerivedTable, restOrm, restRequest, setCache, sortAndSerializeQueryObject, stContains, timeout, toastOptions, toastOptionsDevs, userCustomClearCache, warn };
3587
+ export { A, AggregateBuilder, C6C, C6Constants, ConditionBuilder, DELETE, DeleteQueryBuilder, Executor, ExpressHandler, F, GET, HttpExecutor, JoinBuilder, POST, PUT, PaginationBuilder, PostQueryBuilder, SelectQueryBuilder, SqlExecutor, TestRestfulResponse, UpdateQueryBuilder, apiRequestCache, axiosInstance, bbox, carbonNodeQsStringify, checkAllRequestsComplete, checkCache, clearCache, convertForRequestBody, convertHexIfBinary, derivedTable, determineRuntimeJsType, distSphere, eFetchDependencies, error, fieldEq, getEnvVar, getPrimaryKeyTypes, group, info, isDerivedTableKey, isLocal, isNode, isTest, isVerbose, loadSqlAllowList, normalizeSingularRequest, normalizeSql, onError, onSuccess, removeInvalidKeys, removePrefixIfExists, resolveDerivedTable, restOrm, restRequest, setCache, sortAndSerializeQueryObject, stContains, timeout, toastOptions, toastOptionsDevs, userCustomClearCache, warn };
3331
3588
  //# sourceMappingURL=index.esm.js.map