@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
@@ -11,6 +11,11 @@ export declare class SqlExecutor<G extends OrmGenerics> extends Executor<G> {
11
11
  [key: string]: any;
12
12
  }): string;
13
13
  private formatValue;
14
+ private stripRequestMetadata;
15
+ private normalizeRequestPayload;
16
+ private extractRequestBody;
17
+ private extractPrimaryKeyValues;
18
+ private broadcastWebsocketIfConfigured;
14
19
  runQuery(): Promise<{
15
20
  rest: any;
16
21
  sql: {
@@ -26,4 +31,5 @@ export declare class SqlExecutor<G extends OrmGenerics> extends Executor<G> {
26
31
  values: any;
27
32
  };
28
33
  }>;
34
+ private validateSqlAllowList;
29
35
  }
@@ -1,7 +1,8 @@
1
1
  import { Request, Response, NextFunction } from "express";
2
2
  import { Pool } from "mysql2/promise";
3
3
  import { iC6Object } from "../types/ormInterfaces";
4
- export declare function ExpressHandler({ C6, mysqlPool }: {
4
+ export declare function ExpressHandler({ C6, mysqlPool, sqlAllowListPath, }: {
5
5
  C6: iC6Object;
6
6
  mysqlPool: Pool;
7
+ sqlAllowListPath?: string;
7
8
  }): (req: Request, res: Response, next: NextFunction) => Promise<void>;
@@ -27,6 +27,8 @@ export declare abstract class ConditionBuilder<G extends OrmGenerics> extends Ag
27
27
  private serializeOperand;
28
28
  private isPlainArrayLiteral;
29
29
  private isPlainObjectLiteral;
30
+ private resolveColumnDefinition;
31
+ private isJsonColumn;
30
32
  protected serializeUpdateValue(value: any, params: any[] | Record<string, any>, contextColumn?: string): string;
31
33
  private ensurePlainObject;
32
34
  private resolveExistsInnerColumn;
@@ -135,6 +135,16 @@ export interface iGetC6RestResponse<ResponseDataType extends {
135
135
  export type DetermineResponseDataType<Method extends iRestMethods, RestTableInterface extends {
136
136
  [key: string]: any;
137
137
  }, ResponseDataOverrides = {}> = (Method extends 'POST' ? iPostC6RestResponse<RestTableInterface> : Method extends 'GET' ? iGetC6RestResponse<RestTableInterface, ResponseDataOverrides> : Method extends 'PUT' ? iPutC6RestResponse<RestTableInterface> : Method extends 'DELETE' ? iDeleteC6RestResponse<RestTableInterface> : never);
138
+ export type iRestWebsocketPayload = {
139
+ REST: {
140
+ TABLE_NAME: string;
141
+ TABLE_PREFIX: string;
142
+ METHOD: iRestMethods;
143
+ REQUEST: Record<string, any>;
144
+ REQUEST_PRIMARY_KEY: Record<string, any> | null;
145
+ };
146
+ };
147
+ export type tWebsocketBroadcast = (payload: iRestWebsocketPayload) => void | Promise<void>;
138
148
  export interface iRest<RestShortTableName extends string = any, RestTableInterface extends Record<string, any> = any, PrimaryKey extends keyof RestTableInterface & string = keyof RestTableInterface & string> {
139
149
  C6: iC6Object;
140
150
  axios?: AxiosInstance;
@@ -146,7 +156,9 @@ export interface iRest<RestShortTableName extends string = any, RestTableInterfa
146
156
  requestMethod: iRestMethods;
147
157
  clearCache?: () => void;
148
158
  skipPrimaryCheck?: boolean;
159
+ websocketBroadcast?: tWebsocketBroadcast;
149
160
  verbose?: boolean;
161
+ sqlAllowListPath?: string;
150
162
  }
151
163
  export interface iConstraint {
152
164
  TABLE: string;
@@ -0,0 +1,2 @@
1
+ export declare const normalizeSql: (sql: string) => string;
2
+ export declare const loadSqlAllowList: (allowListPath: string) => Promise<Set<string>>;
package/dist/index.cjs.js CHANGED
@@ -1827,8 +1827,9 @@ var ConditionBuilder = /** @class */ (function (_super) {
1827
1827
  }
1828
1828
  throw new Error('Unsupported operand type in SQL expression.');
1829
1829
  };
1830
- ConditionBuilder.prototype.isPlainArrayLiteral = function (value) {
1830
+ ConditionBuilder.prototype.isPlainArrayLiteral = function (value, allowColumnRefs) {
1831
1831
  var _this = this;
1832
+ if (allowColumnRefs === void 0) { allowColumnRefs = false; }
1832
1833
  if (!Array.isArray(value))
1833
1834
  return false;
1834
1835
  return value.every(function (item) {
@@ -1838,14 +1839,15 @@ var ConditionBuilder = /** @class */ (function (_super) {
1838
1839
  if (type === 'string' || type === 'number' || type === 'boolean')
1839
1840
  return true;
1840
1841
  if (Array.isArray(item))
1841
- return _this.isPlainArrayLiteral(item);
1842
+ return _this.isPlainArrayLiteral(item, allowColumnRefs);
1842
1843
  if (item && typeof item === 'object')
1843
- return _this.isPlainObjectLiteral(item);
1844
+ return _this.isPlainObjectLiteral(item, allowColumnRefs);
1844
1845
  return false;
1845
1846
  });
1846
1847
  };
1847
- ConditionBuilder.prototype.isPlainObjectLiteral = function (value) {
1848
+ ConditionBuilder.prototype.isPlainObjectLiteral = function (value, allowColumnRefs) {
1848
1849
  var _this = this;
1850
+ if (allowColumnRefs === void 0) { allowColumnRefs = false; }
1849
1851
  if (!value || typeof value !== 'object' || Array.isArray(value))
1850
1852
  return false;
1851
1853
  if (value instanceof Date)
@@ -1864,17 +1866,37 @@ var ConditionBuilder = /** @class */ (function (_super) {
1864
1866
  })) {
1865
1867
  return false;
1866
1868
  }
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;
1869
+ if (!allowColumnRefs) {
1870
+ if (entries.some(function (_a) {
1871
+ var key = _a[0];
1872
+ return typeof key === 'string' && (_this.isColumnRef(key) || key.includes('.'));
1873
+ })) {
1874
+ return false;
1875
+ }
1872
1876
  }
1873
1877
  return true;
1874
1878
  };
1879
+ ConditionBuilder.prototype.resolveColumnDefinition = function (column) {
1880
+ var _a, _b, _c, _d;
1881
+ if (!column || typeof column !== 'string' || !column.includes('.'))
1882
+ return undefined;
1883
+ var _e = column.split('.', 2), prefix = _e[0], colName = _e[1];
1884
+ var tableName = (_a = this.aliasMap[prefix]) !== null && _a !== void 0 ? _a : prefix;
1885
+ var table = (_c = (_b = this.config.C6) === null || _b === void 0 ? void 0 : _b.TABLES) === null || _c === void 0 ? void 0 : _c[tableName];
1886
+ if (!(table === null || table === void 0 ? void 0 : table.TYPE_VALIDATION))
1887
+ return undefined;
1888
+ return (_d = table.TYPE_VALIDATION[colName]) !== null && _d !== void 0 ? _d : table.TYPE_VALIDATION["".concat(tableName, ".").concat(colName)];
1889
+ };
1890
+ ConditionBuilder.prototype.isJsonColumn = function (column) {
1891
+ var columnDef = this.resolveColumnDefinition(column);
1892
+ var mysqlType = columnDef === null || columnDef === void 0 ? void 0 : columnDef.MYSQL_TYPE;
1893
+ return typeof mysqlType === 'string' && mysqlType.toLowerCase().includes('json');
1894
+ };
1875
1895
  ConditionBuilder.prototype.serializeUpdateValue = function (value, params, contextColumn) {
1876
1896
  var normalized = value instanceof Map ? Object.fromEntries(value) : value;
1877
- if (this.isPlainArrayLiteral(normalized) || this.isPlainObjectLiteral(normalized)) {
1897
+ var allowColumnRefs = this.isJsonColumn(contextColumn);
1898
+ if (this.isPlainArrayLiteral(normalized, allowColumnRefs)
1899
+ || this.isPlainObjectLiteral(normalized, allowColumnRefs)) {
1878
1900
  return this.addParam(params, contextColumn !== null && contextColumn !== void 0 ? contextColumn : '', JSON.stringify(normalized));
1879
1901
  }
1880
1902
  var _a = this.serializeOperand(normalized, params, contextColumn), sql = _a.sql, isReference = _a.isReference, isExpression = _a.isExpression, isSubSelect = _a.isSubSelect;
@@ -2941,6 +2963,63 @@ function normalizeSingularRequest(requestMethod, request, restModel, removedPrim
2941
2963
  return tslib.__assign(tslib.__assign({}, normalized), { dataInsertMultipleRows: dataInsertMultipleRows, cacheResults: cacheResults, fetchDependencies: fetchDependencies, debug: debug, success: success, error: error });
2942
2964
  }
2943
2965
 
2966
+ var allowListCache = new Map();
2967
+ var normalizeSql = function (sql) {
2968
+ return sql.replace(/\s+/g, " ").trim();
2969
+ };
2970
+ var parseAllowList = function (raw, sourcePath) {
2971
+ var parsed;
2972
+ try {
2973
+ parsed = JSON.parse(raw);
2974
+ }
2975
+ catch (error) {
2976
+ throw new Error("SQL allowlist at ".concat(sourcePath, " is not valid JSON."));
2977
+ }
2978
+ if (!Array.isArray(parsed)) {
2979
+ throw new Error("SQL allowlist at ".concat(sourcePath, " must be a JSON array of strings."));
2980
+ }
2981
+ var sqlEntries = parsed
2982
+ .filter(function (entry) { return typeof entry === "string"; })
2983
+ .map(normalizeSql)
2984
+ .filter(function (entry) { return entry.length > 0; });
2985
+ if (sqlEntries.length !== parsed.length) {
2986
+ throw new Error("SQL allowlist at ".concat(sourcePath, " must contain only string entries."));
2987
+ }
2988
+ return sqlEntries;
2989
+ };
2990
+ var loadSqlAllowList = function (allowListPath) { return tslib.__awaiter(void 0, void 0, void 0, function () {
2991
+ var readFile, raw, sqlEntries, allowList;
2992
+ return tslib.__generator(this, function (_a) {
2993
+ switch (_a.label) {
2994
+ case 0:
2995
+ if (allowListCache.has(allowListPath)) {
2996
+ return [2 /*return*/, allowListCache.get(allowListPath)];
2997
+ }
2998
+ if (!isNode()) {
2999
+ throw new Error("SQL allowlist validation requires a Node runtime.");
3000
+ }
3001
+ return [4 /*yield*/, import('node:fs/promises')];
3002
+ case 1:
3003
+ readFile = (_a.sent()).readFile;
3004
+ _a.label = 2;
3005
+ case 2:
3006
+ _a.trys.push([2, 4, , 5]);
3007
+ return [4 /*yield*/, readFile(allowListPath, "utf-8")];
3008
+ case 3:
3009
+ raw = _a.sent();
3010
+ return [3 /*break*/, 5];
3011
+ case 4:
3012
+ _a.sent();
3013
+ throw new Error("SQL allowlist file not found at ".concat(allowListPath, "."));
3014
+ case 5:
3015
+ sqlEntries = parseAllowList(raw, allowListPath);
3016
+ allowList = new Set(sqlEntries);
3017
+ allowListCache.set(allowListPath, allowList);
3018
+ return [2 /*return*/, allowList];
3019
+ }
3020
+ });
3021
+ }); };
3022
+
2944
3023
  var SqlExecutor = /** @class */ (function (_super) {
2945
3024
  tslib.__extends(SqlExecutor, _super);
2946
3025
  function SqlExecutor() {
@@ -2973,10 +3052,10 @@ var SqlExecutor = /** @class */ (function (_super) {
2973
3052
  switch (_a) {
2974
3053
  case 'GET': return [3 /*break*/, 1];
2975
3054
  case 'POST': return [3 /*break*/, 3];
2976
- case 'PUT': return [3 /*break*/, 5];
2977
- case 'DELETE': return [3 /*break*/, 7];
3055
+ case 'PUT': return [3 /*break*/, 6];
3056
+ case 'DELETE': return [3 /*break*/, 9];
2978
3057
  }
2979
- return [3 /*break*/, 9];
3058
+ return [3 /*break*/, 12];
2980
3059
  case 1: return [4 /*yield*/, this.runQuery()];
2981
3060
  case 2:
2982
3061
  rest = _b.sent();
@@ -2984,16 +3063,25 @@ var SqlExecutor = /** @class */ (function (_super) {
2984
3063
  case 3: return [4 /*yield*/, this.runQuery()];
2985
3064
  case 4:
2986
3065
  result = _b.sent();
3066
+ return [4 /*yield*/, this.broadcastWebsocketIfConfigured()];
3067
+ case 5:
3068
+ _b.sent();
2987
3069
  return [2 /*return*/, result];
2988
- case 5: return [4 /*yield*/, this.runQuery()];
2989
- case 6:
3070
+ case 6: return [4 /*yield*/, this.runQuery()];
3071
+ case 7:
2990
3072
  result = _b.sent();
2991
- return [2 /*return*/, result];
2992
- case 7: return [4 /*yield*/, this.runQuery()];
3073
+ return [4 /*yield*/, this.broadcastWebsocketIfConfigured()];
2993
3074
  case 8:
3075
+ _b.sent();
3076
+ return [2 /*return*/, result];
3077
+ case 9: return [4 /*yield*/, this.runQuery()];
3078
+ case 10:
2994
3079
  result = _b.sent();
3080
+ return [4 /*yield*/, this.broadcastWebsocketIfConfigured()];
3081
+ case 11:
3082
+ _b.sent();
2995
3083
  return [2 /*return*/, result];
2996
- case 9: throw new Error("Unsupported request method: ".concat(method));
3084
+ case 12: throw new Error("Unsupported request method: ".concat(method));
2997
3085
  }
2998
3086
  });
2999
3087
  });
@@ -3054,6 +3142,149 @@ var SqlExecutor = /** @class */ (function (_super) {
3054
3142
  return "'".concat(val.toISOString().slice(0, 19).replace('T', ' '), "'");
3055
3143
  return "'".concat(JSON.stringify(val), "'");
3056
3144
  };
3145
+ SqlExecutor.prototype.stripRequestMetadata = function (source) {
3146
+ var ignoredKeys = new Set([
3147
+ C6Constants.SELECT,
3148
+ C6Constants.UPDATE,
3149
+ C6Constants.DELETE,
3150
+ C6Constants.WHERE,
3151
+ C6Constants.JOIN,
3152
+ C6Constants.PAGINATION,
3153
+ C6Constants.INSERT,
3154
+ C6Constants.REPLACE,
3155
+ "dataInsertMultipleRows",
3156
+ "cacheResults",
3157
+ "fetchDependencies",
3158
+ "debug",
3159
+ "success",
3160
+ "error",
3161
+ ]);
3162
+ var filtered = {};
3163
+ for (var _i = 0, _a = Object.entries(source); _i < _a.length; _i++) {
3164
+ var _b = _a[_i], key = _b[0], value = _b[1];
3165
+ if (!ignoredKeys.has(key)) {
3166
+ filtered[key] = value;
3167
+ }
3168
+ }
3169
+ return filtered;
3170
+ };
3171
+ SqlExecutor.prototype.normalizeRequestPayload = function (source) {
3172
+ var _a;
3173
+ var columns = this.config.restModel.COLUMNS;
3174
+ var validColumns = new Set(Object.values(columns));
3175
+ var normalized = {};
3176
+ for (var _i = 0, _b = Object.entries(source); _i < _b.length; _i++) {
3177
+ var _c = _b[_i], key = _c[0], value = _c[1];
3178
+ var shortKey = (_a = columns[key]) !== null && _a !== void 0 ? _a : (key.includes(".") ? key.split(".").pop() : key);
3179
+ if (validColumns.has(shortKey)) {
3180
+ normalized[shortKey] = value;
3181
+ }
3182
+ }
3183
+ return normalized;
3184
+ };
3185
+ SqlExecutor.prototype.extractRequestBody = function () {
3186
+ var _a, _b;
3187
+ var request = this.request;
3188
+ if (this.config.requestMethod === C6Constants.POST) {
3189
+ if (Array.isArray(request.dataInsertMultipleRows) && request.dataInsertMultipleRows.length > 0) {
3190
+ return request.dataInsertMultipleRows[0];
3191
+ }
3192
+ if (C6Constants.INSERT in request) {
3193
+ return (_a = request[C6Constants.INSERT]) !== null && _a !== void 0 ? _a : {};
3194
+ }
3195
+ if (C6Constants.REPLACE in request) {
3196
+ return (_b = request[C6Constants.REPLACE]) !== null && _b !== void 0 ? _b : {};
3197
+ }
3198
+ return this.stripRequestMetadata(request);
3199
+ }
3200
+ if (this.config.requestMethod === C6Constants.PUT) {
3201
+ if (request[C6Constants.UPDATE] && typeof request[C6Constants.UPDATE] === "object") {
3202
+ return request[C6Constants.UPDATE];
3203
+ }
3204
+ return this.stripRequestMetadata(request);
3205
+ }
3206
+ return {};
3207
+ };
3208
+ SqlExecutor.prototype.extractPrimaryKeyValues = function () {
3209
+ var _a, _b, _c;
3210
+ var request = this.request;
3211
+ var where = request === null || request === void 0 ? void 0 : request[C6Constants.WHERE];
3212
+ var sources = [request, (where && typeof where === "object" && !Array.isArray(where)) ? where : undefined];
3213
+ var columns = this.config.restModel.COLUMNS;
3214
+ var primaryShorts = (_a = this.config.restModel.PRIMARY_SHORT) !== null && _a !== void 0 ? _a : [];
3215
+ var primaryFulls = (_b = this.config.restModel.PRIMARY) !== null && _b !== void 0 ? _b : [];
3216
+ var pkValues = {};
3217
+ var _loop_1 = function (pkShort) {
3218
+ var value = undefined;
3219
+ for (var _d = 0, sources_1 = sources; _d < sources_1.length; _d++) {
3220
+ var source = sources_1[_d];
3221
+ if (source && pkShort in source) {
3222
+ value = source[pkShort];
3223
+ break;
3224
+ }
3225
+ }
3226
+ if (value === undefined) {
3227
+ 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; });
3228
+ if (fullKey) {
3229
+ for (var _e = 0, sources_2 = sources; _e < sources_2.length; _e++) {
3230
+ var source = sources_2[_e];
3231
+ if (source && fullKey in source) {
3232
+ value = source[fullKey];
3233
+ break;
3234
+ }
3235
+ }
3236
+ }
3237
+ }
3238
+ if (value !== undefined) {
3239
+ pkValues[pkShort] = value;
3240
+ }
3241
+ };
3242
+ for (var _i = 0, primaryShorts_1 = primaryShorts; _i < primaryShorts_1.length; _i++) {
3243
+ var pkShort = primaryShorts_1[_i];
3244
+ _loop_1(pkShort);
3245
+ }
3246
+ if (primaryShorts.length > 0 && Object.keys(pkValues).length < primaryShorts.length) {
3247
+ return null;
3248
+ }
3249
+ return Object.keys(pkValues).length > 0 ? pkValues : null;
3250
+ };
3251
+ SqlExecutor.prototype.broadcastWebsocketIfConfigured = function () {
3252
+ return tslib.__awaiter(this, void 0, void 0, function () {
3253
+ var broadcast, payload, error_1;
3254
+ var _a, _b;
3255
+ return tslib.__generator(this, function (_c) {
3256
+ switch (_c.label) {
3257
+ case 0:
3258
+ broadcast = this.config.websocketBroadcast;
3259
+ if (!broadcast || this.config.requestMethod === C6Constants.GET)
3260
+ return [2 /*return*/];
3261
+ payload = {
3262
+ REST: {
3263
+ TABLE_NAME: this.config.restModel.TABLE_NAME,
3264
+ TABLE_PREFIX: (_b = (_a = this.config.C6) === null || _a === void 0 ? void 0 : _a.PREFIX) !== null && _b !== void 0 ? _b : "",
3265
+ METHOD: this.config.requestMethod,
3266
+ REQUEST: this.normalizeRequestPayload(this.extractRequestBody()),
3267
+ REQUEST_PRIMARY_KEY: this.extractPrimaryKeyValues(),
3268
+ },
3269
+ };
3270
+ _c.label = 1;
3271
+ case 1:
3272
+ _c.trys.push([1, 3, , 4]);
3273
+ return [4 /*yield*/, broadcast(payload)];
3274
+ case 2:
3275
+ _c.sent();
3276
+ return [3 /*break*/, 4];
3277
+ case 3:
3278
+ error_1 = _c.sent();
3279
+ if (this.config.verbose) {
3280
+ console.error("[SQL EXECUTOR] websocketBroadcast failed", error_1);
3281
+ }
3282
+ return [3 /*break*/, 4];
3283
+ case 4: return [2 /*return*/];
3284
+ }
3285
+ });
3286
+ });
3287
+ };
3057
3288
  SqlExecutor.prototype.runQuery = function () {
3058
3289
  return tslib.__awaiter(this, void 0, void 0, function () {
3059
3290
  var TABLE_NAME, method, builder, QueryResult, formatted, toUnnamed, _a, sql, values;
@@ -3085,6 +3316,9 @@ var SqlExecutor = /** @class */ (function (_super) {
3085
3316
  this.config.verbose && console.log("[SQL EXECUTOR] \uD83E\uDDE0 Formatted ".concat(method.toUpperCase(), " SQL:"), formatted);
3086
3317
  toUnnamed = namedPlaceholders();
3087
3318
  _a = toUnnamed(QueryResult.sql, QueryResult.params), sql = _a[0], values = _a[1];
3319
+ return [4 /*yield*/, this.validateSqlAllowList(sql)];
3320
+ case 1:
3321
+ _b.sent();
3088
3322
  return [4 /*yield*/, this.withConnection(function (conn) { return tslib.__awaiter(_this, void 0, void 0, function () {
3089
3323
  var result;
3090
3324
  return tslib.__generator(this, function (_a) {
@@ -3109,7 +3343,29 @@ var SqlExecutor = /** @class */ (function (_super) {
3109
3343
  }
3110
3344
  });
3111
3345
  }); })];
3112
- case 1: return [2 /*return*/, _b.sent()];
3346
+ case 2: return [2 /*return*/, _b.sent()];
3347
+ }
3348
+ });
3349
+ });
3350
+ };
3351
+ SqlExecutor.prototype.validateSqlAllowList = function (sql) {
3352
+ return tslib.__awaiter(this, void 0, void 0, function () {
3353
+ var allowListPath, allowList, normalized;
3354
+ return tslib.__generator(this, function (_a) {
3355
+ switch (_a.label) {
3356
+ case 0:
3357
+ allowListPath = this.config.sqlAllowListPath;
3358
+ if (!allowListPath) {
3359
+ return [2 /*return*/];
3360
+ }
3361
+ return [4 /*yield*/, loadSqlAllowList(allowListPath)];
3362
+ case 1:
3363
+ allowList = _a.sent();
3364
+ normalized = normalizeSql(sql);
3365
+ if (!allowList.has(normalized)) {
3366
+ throw new Error("SQL statement is not permitted by allowlist (".concat(allowListPath, ")."));
3367
+ }
3368
+ return [2 /*return*/];
3113
3369
  }
3114
3370
  });
3115
3371
  });
@@ -3126,7 +3382,7 @@ var SqlExecutor$1 = /*#__PURE__*/Object.freeze({
3126
3382
  // note sure how it would help anyone actually...
3127
3383
  function ExpressHandler(_a) {
3128
3384
  var _this = this;
3129
- var C6 = _a.C6, mysqlPool = _a.mysqlPool;
3385
+ var C6 = _a.C6, mysqlPool = _a.mysqlPool, sqlAllowListPath = _a.sqlAllowListPath;
3130
3386
  return function (req, res, next) { return tslib.__awaiter(_this, void 0, void 0, function () {
3131
3387
  var incomingMethod, table, primary, methodOverrideRaw, methodOverride, treatAsGet, method, payload, restModel, primaryKeys_1, primaryShortKeys_1, columnMap_1, resolveShortKey_1, hasPrimaryKeyValues, primaryKeyName, response, err_1;
3132
3388
  var _a, _b, _c, _d, _e, _f, _g;
@@ -3212,6 +3468,7 @@ function ExpressHandler(_a) {
3212
3468
  return [4 /*yield*/, restRequest({
3213
3469
  C6: C6,
3214
3470
  mysqlPool: mysqlPool,
3471
+ sqlAllowListPath: sqlAllowListPath,
3215
3472
  requestMethod: method,
3216
3473
  restModel: C6.TABLES[table]
3217
3474
  })(payload)];
@@ -3374,7 +3631,9 @@ exports.isLocal = isLocal;
3374
3631
  exports.isNode = isNode;
3375
3632
  exports.isTest = isTest;
3376
3633
  exports.isVerbose = isVerbose;
3634
+ exports.loadSqlAllowList = loadSqlAllowList;
3377
3635
  exports.normalizeSingularRequest = normalizeSingularRequest;
3636
+ exports.normalizeSql = normalizeSql;
3378
3637
  exports.onError = onError;
3379
3638
  exports.onSuccess = onSuccess;
3380
3639
  exports.removeInvalidKeys = removeInvalidKeys;