@carbonorm/carbonnode 6.0.10 → 6.0.13

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 (75) hide show
  1. package/dist/executors/SqlExecutor.d.ts +6 -0
  2. package/dist/handlers/ExpressHandler.d.ts +8 -9
  3. package/dist/index.cjs.js +324 -108
  4. package/dist/index.cjs.js.map +1 -1
  5. package/dist/index.esm.js +324 -109
  6. package/dist/index.esm.js.map +1 -1
  7. package/dist/types/ormInterfaces.d.ts +25 -5
  8. package/dist/utils/cacheManager.d.ts +2 -3
  9. package/package.json +1 -1
  10. package/src/__tests__/convertForRequestBody.test.ts +58 -0
  11. package/src/__tests__/expressServer.e2e.test.ts +62 -38
  12. package/src/__tests__/fixtures/createTestServer.ts +7 -3
  13. package/src/__tests__/httpExecutorSingular.e2e.test.ts +97 -60
  14. package/src/__tests__/logSql.test.ts +13 -0
  15. package/src/__tests__/sakila-db/C6.js +1 -1
  16. package/src/__tests__/sakila-db/C6.mysqldump.json +1 -1
  17. package/src/__tests__/sakila-db/C6.mysqldump.sql +1 -1
  18. package/src/__tests__/sakila-db/C6.sqlAllowList.json +11 -11
  19. package/src/__tests__/sakila-db/C6.ts +1 -1
  20. package/src/__tests__/sakila-db/sqlResponses/C6.actor.post.json +4 -4
  21. package/src/__tests__/sakila-db/sqlResponses/C6.actor.post.latest.json +3 -3
  22. package/src/__tests__/sakila-db/sqlResponses/C6.actor.put.json +1 -1
  23. package/src/__tests__/sakila-db/sqlResponses/C6.actor.put.lookup.json +3 -3
  24. package/src/__tests__/sakila-db/sqlResponses/C6.address.post.json +6 -6
  25. package/src/__tests__/sakila-db/sqlResponses/C6.address.post.latest.json +5 -5
  26. package/src/__tests__/sakila-db/sqlResponses/C6.address.put.json +1 -1
  27. package/src/__tests__/sakila-db/sqlResponses/C6.address.put.lookup.json +5 -5
  28. package/src/__tests__/sakila-db/sqlResponses/C6.category.post.json +3 -3
  29. package/src/__tests__/sakila-db/sqlResponses/C6.category.post.latest.json +2 -2
  30. package/src/__tests__/sakila-db/sqlResponses/C6.category.put.json +1 -1
  31. package/src/__tests__/sakila-db/sqlResponses/C6.category.put.lookup.json +2 -2
  32. package/src/__tests__/sakila-db/sqlResponses/C6.city.post.json +3 -3
  33. package/src/__tests__/sakila-db/sqlResponses/C6.city.post.latest.json +2 -2
  34. package/src/__tests__/sakila-db/sqlResponses/C6.city.put.json +1 -1
  35. package/src/__tests__/sakila-db/sqlResponses/C6.city.put.lookup.json +2 -2
  36. package/src/__tests__/sakila-db/sqlResponses/C6.country.post.json +3 -3
  37. package/src/__tests__/sakila-db/sqlResponses/C6.country.post.latest.json +2 -2
  38. package/src/__tests__/sakila-db/sqlResponses/C6.country.put.json +1 -1
  39. package/src/__tests__/sakila-db/sqlResponses/C6.country.put.lookup.json +2 -2
  40. package/src/__tests__/sakila-db/sqlResponses/C6.customer.post.json +6 -6
  41. package/src/__tests__/sakila-db/sqlResponses/C6.customer.post.latest.json +5 -5
  42. package/src/__tests__/sakila-db/sqlResponses/C6.customer.put.json +1 -1
  43. package/src/__tests__/sakila-db/sqlResponses/C6.customer.put.lookup.json +5 -5
  44. package/src/__tests__/sakila-db/sqlResponses/C6.film.post.json +3 -3
  45. package/src/__tests__/sakila-db/sqlResponses/C6.film.post.latest.json +2 -2
  46. package/src/__tests__/sakila-db/sqlResponses/C6.film.put.json +1 -1
  47. package/src/__tests__/sakila-db/sqlResponses/C6.film.put.lookup.json +2 -2
  48. package/src/__tests__/sakila-db/sqlResponses/C6.inventory.post.json +2 -2
  49. package/src/__tests__/sakila-db/sqlResponses/C6.inventory.post.latest.json +1 -1
  50. package/src/__tests__/sakila-db/sqlResponses/C6.inventory.put.json +1 -1
  51. package/src/__tests__/sakila-db/sqlResponses/C6.inventory.put.lookup.json +1 -1
  52. package/src/__tests__/sakila-db/sqlResponses/C6.language.post.json +3 -3
  53. package/src/__tests__/sakila-db/sqlResponses/C6.language.post.latest.json +2 -2
  54. package/src/__tests__/sakila-db/sqlResponses/C6.language.put.json +1 -1
  55. package/src/__tests__/sakila-db/sqlResponses/C6.language.put.lookup.json +2 -2
  56. package/src/__tests__/sakila-db/sqlResponses/C6.payment.post.json +3 -3
  57. package/src/__tests__/sakila-db/sqlResponses/C6.payment.post.latest.json +2 -2
  58. package/src/__tests__/sakila-db/sqlResponses/C6.payment.put.lookup.json +2 -2
  59. package/src/__tests__/sakila-db/sqlResponses/C6.rental.post.json +4 -4
  60. package/src/__tests__/sakila-db/sqlResponses/C6.rental.post.latest.json +3 -3
  61. package/src/__tests__/sakila-db/sqlResponses/C6.rental.put.json +1 -1
  62. package/src/__tests__/sakila-db/sqlResponses/C6.rental.put.lookup.json +3 -3
  63. package/src/__tests__/sakila.generated.test.ts +11 -3
  64. package/src/__tests__/sqlBuilders.test.ts +46 -0
  65. package/src/__tests__/sqlExecutorLifecycleHooks.test.ts +122 -0
  66. package/src/api/convertForRequestBody.ts +9 -2
  67. package/src/api/restRequest.ts +1 -0
  68. package/src/executors/HttpExecutor.ts +1 -1
  69. package/src/executors/SqlExecutor.ts +252 -49
  70. package/src/handlers/ExpressHandler.ts +50 -24
  71. package/src/orm/builders/ConditionBuilder.ts +43 -1
  72. package/src/orm/queries/PostQueryBuilder.ts +24 -12
  73. package/src/types/ormInterfaces.ts +31 -5
  74. package/src/utils/cacheManager.ts +3 -4
  75. package/src/utils/colorSql.ts +18 -0
@@ -18,5 +18,11 @@ export declare class SqlExecutor<G extends OrmGenerics> extends Executor<G> {
18
18
  private extractPrimaryKeyValuesFromData;
19
19
  private broadcastWebsocketIfConfigured;
20
20
  runQuery(): Promise<DetermineResponseDataType<G['RequestMethod'], G['RestTableInterface']>>;
21
+ private getQueryBuilder;
22
+ private buildSqlExecutionContext;
23
+ private createResponseFromQueryResult;
24
+ private createLifecycleHookResponse;
25
+ private createCacheResponseEnvelope;
26
+ private executeQueryWithLifecycle;
21
27
  private validateSqlAllowList;
22
28
  }
@@ -1,9 +1,8 @@
1
- import type { Request, Response, NextFunction } from "express";
2
- import type { Pool } from "mysql2/promise";
3
- import type { iC6Object, tWebsocketBroadcast } from "../types/ormInterfaces";
4
- export declare function ExpressHandler({ C6, mysqlPool, sqlAllowListPath, websocketBroadcast, }: {
5
- C6: iC6Object;
6
- mysqlPool: Pool;
7
- sqlAllowListPath?: string;
8
- websocketBroadcast?: tWebsocketBroadcast;
9
- }): (req: Request, res: Response, next: NextFunction) => Promise<void>;
1
+ import type { Request, Response, Router } from "express";
2
+ import { iRest } from "../types/ormInterfaces";
3
+ import { OrmGenerics } from "../types/ormGenerics";
4
+ export declare function restExpressRequest<G extends OrmGenerics>(routerConfig: {
5
+ router: Pick<Router, "all">;
6
+ routePath?: string;
7
+ } & Omit<iRest<G['RestShortTableName'], G['RestTableInterface']>, "requestMethod" | "restModel">): void;
8
+ export declare function ExpressHandler<G extends OrmGenerics>(configX: (() => Omit<iRest<G['RestShortTableName'], G['RestTableInterface']>, "requestMethod" | "restModel">) | Omit<iRest<G['RestShortTableName'], G['RestTableInterface']>, "requestMethod" | "restModel">): (req: Request, res: Response) => Promise<void>;
package/dist/index.cjs.js CHANGED
@@ -536,7 +536,11 @@ var logWithLevel = function (requiredLevel, context, logger) {
536
536
  };
537
537
 
538
538
  function convertForRequestBody (restfulObject, tableName, C6, regexErrorHandler) {
539
- if (regexErrorHandler === void 0) { regexErrorHandler = alert; }
539
+ if (regexErrorHandler === void 0) { regexErrorHandler = function (message) {
540
+ if (typeof globalThis !== "undefined" && typeof globalThis.alert === "function") {
541
+ globalThis.alert(message);
542
+ }
543
+ }; }
540
544
  var payload = {};
541
545
  var tableNames = Array.isArray(tableName) ? tableName : [tableName];
542
546
  var tableDefinitions = tableNames.map(function (name) {
@@ -559,7 +563,8 @@ function convertForRequestBody (restfulObject, tableName, C6, regexErrorHandler)
559
563
  C6Constants.DELETE,
560
564
  C6Constants.WHERE,
561
565
  C6Constants.JOIN,
562
- C6Constants.PAGINATION
566
+ C6Constants.PAGINATION,
567
+ "cacheResults",
563
568
  ].includes(value)) {
564
569
  var val_1 = restfulObject[value];
565
570
  if (Array.isArray(val_1)) {
@@ -573,6 +578,9 @@ function convertForRequestBody (restfulObject, tableName, C6, regexErrorHandler)
573
578
  return (tslib.__assign(tslib.__assign({}, acc), (_a = {}, _a[key] = val_1[key], _a)));
574
579
  }, {});
575
580
  }
581
+ else {
582
+ payload[value] = val_1;
583
+ }
576
584
  return "continue";
577
585
  }
578
586
  if (shortReference in tableDefinition) {
@@ -904,7 +912,7 @@ function checkCache(method, tableName, requestData) {
904
912
  var cached = apiRequestCache.get(key);
905
913
  if (!cached)
906
914
  return false;
907
- if (shouldLog(exports.LogLevel.DEBUG, undefined)) {
915
+ if (shouldLog(exports.LogLevel.INFO, undefined)) {
908
916
  console.groupCollapsed("%c API cache hit for ".concat(method, " ").concat(tableName), "color:#0c0");
909
917
  console.log("Request Data:", requestData);
910
918
  console.groupEnd();
@@ -2131,14 +2139,51 @@ var ConditionBuilder = /** @class */ (function (_super) {
2131
2139
  return typeof mysqlType === 'string' && mysqlType.toLowerCase().includes('json');
2132
2140
  };
2133
2141
  ConditionBuilder.prototype.serializeUpdateValue = function (value, params, contextColumn) {
2142
+ var _a;
2143
+ var _this = this;
2134
2144
  var normalized = value instanceof Map ? Object.fromEntries(value) : value;
2135
2145
  var allowColumnRefs = this.isJsonColumn(contextColumn);
2136
2146
  if (this.isPlainArrayLiteral(normalized, allowColumnRefs)
2137
2147
  || this.isPlainObjectLiteral(normalized, allowColumnRefs)) {
2138
2148
  return this.addParam(params, contextColumn !== null && contextColumn !== void 0 ? contextColumn : '', JSON.stringify(normalized));
2139
2149
  }
2140
- var _a = this.serializeOperand(normalized, params, contextColumn), sql = _a.sql, isReference = _a.isReference, isExpression = _a.isExpression, isSubSelect = _a.isSubSelect;
2150
+ var sql;
2151
+ var isReference;
2152
+ var isExpression;
2153
+ var isSubSelect;
2154
+ var shouldStringifyObjectFallback = function (candidate) {
2155
+ if (typeof candidate !== 'object'
2156
+ || candidate === null
2157
+ || candidate instanceof Date
2158
+ || (typeof Buffer !== 'undefined' && Buffer.isBuffer && Buffer.isBuffer(candidate))) {
2159
+ return false;
2160
+ }
2161
+ var normalizedCandidate = candidate instanceof Map
2162
+ ? Object.fromEntries(candidate)
2163
+ : candidate;
2164
+ var entries = Object.entries(normalizedCandidate);
2165
+ if (entries.length !== 1) {
2166
+ return true;
2167
+ }
2168
+ var key = entries[0][0];
2169
+ if (_this.isOperator(key) || _this.BOOLEAN_OPERATORS.has(key)) {
2170
+ return false;
2171
+ }
2172
+ return true;
2173
+ };
2174
+ try {
2175
+ (_a = this.serializeOperand(normalized, params, contextColumn), sql = _a.sql, isReference = _a.isReference, isExpression = _a.isExpression, isSubSelect = _a.isSubSelect);
2176
+ }
2177
+ catch (err) {
2178
+ if (shouldStringifyObjectFallback(normalized)) {
2179
+ return this.addParam(params, contextColumn !== null && contextColumn !== void 0 ? contextColumn : '', JSON.stringify(normalized));
2180
+ }
2181
+ throw err;
2182
+ }
2141
2183
  if (!isReference && !isExpression && !isSubSelect && typeof normalized === 'object' && normalized !== null) {
2184
+ if (shouldStringifyObjectFallback(normalized)) {
2185
+ return this.addParam(params, contextColumn !== null && contextColumn !== void 0 ? contextColumn : '', JSON.stringify(normalized));
2186
+ }
2142
2187
  throw new Error('Unsupported operand type in SQL expression.');
2143
2188
  }
2144
2189
  return sql;
@@ -2952,11 +2997,27 @@ function collapseBinds(sql) {
2952
2997
  return "".concat(C$1.DIM, "? \u00D7").concat(count).concat(RESET);
2953
2998
  });
2954
2999
  }
3000
+ /**
3001
+ * ( ? ×9 ), ( ? ×9 ), ( ? ×9 ) -> ( ? ×9 ) ×3
3002
+ */
3003
+ function collapseRepeatedValueRows(sql) {
3004
+ var repeatedRowPattern = /(\((?:\x1b\[[0-9;]*m)?\?\s*×\d+(?:\x1b\[[0-9;]*m)?\)|\(\s*(?:\?\s*,\s*)+\?\s*\))(?:\s*,\s*\1){2,}/g;
3005
+ return sql.replace(repeatedRowPattern, function (match, row) {
3006
+ var _a, _b;
3007
+ var rowMatches = match.match(new RegExp(row.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "g"));
3008
+ var count = (_a = rowMatches === null || rowMatches === void 0 ? void 0 : rowMatches.length) !== null && _a !== void 0 ? _a : 1;
3009
+ var normalizedRow = row.includes("×")
3010
+ ? row
3011
+ : "(".concat(C$1.DIM, "? \u00D7").concat(((_b = row.match(/\?/g)) !== null && _b !== void 0 ? _b : []).length).concat(RESET, ")");
3012
+ return "".concat(normalizedRow, " ").concat(C$1.DIM, "\u00D7").concat(count).concat(RESET);
3013
+ });
3014
+ }
2955
3015
  /* ---------- main formatter ---------- */
2956
3016
  function colorSql(sql) {
2957
3017
  var s = sql.trim();
2958
3018
  /* 1️⃣ collapse bind noise */
2959
3019
  s = collapseBinds(s);
3020
+ s = collapseRepeatedValueRows(s);
2960
3021
  /* 2️⃣ table.column coloring (core visual grouping) */
2961
3022
  s = s.replace(/\b(`?\w+`?)\.(\w+)\b/g, function (_, table, column) {
2962
3023
  return "".concat(tableColor(table)).concat(table).concat(RESET, ".") +
@@ -2973,7 +3034,7 @@ function colorSql(sql) {
2973
3034
  return s;
2974
3035
  }
2975
3036
 
2976
- var version = "6.0.10";
3037
+ var version = "6.0.13";
2977
3038
 
2978
3039
  var DEFAULT_STEP = 8;
2979
3040
  function parseSemver(version) {
@@ -3160,21 +3221,35 @@ var PostQueryBuilder = /** @class */ (function (_super) {
3160
3221
  };
3161
3222
  PostQueryBuilder.prototype.build = function (table) {
3162
3223
  var _this = this;
3224
+ var _a, _b;
3163
3225
  this.aliasMap = {};
3164
3226
  var verb = C6C.REPLACE in this.request ? C6C.REPLACE : C6C.INSERT;
3165
- var body = verb in this.request ? this.request[verb] : this.request;
3166
- var keys = Object.keys(body);
3227
+ var directRows = Array.isArray(this.request)
3228
+ ? this.request
3229
+ : [];
3230
+ var rows = directRows.length > 0
3231
+ ? directRows
3232
+ : Array.isArray(this.request.dataInsertMultipleRows) &&
3233
+ this.request.dataInsertMultipleRows.length > 0
3234
+ ? this.request.dataInsertMultipleRows
3235
+ : [verb in this.request ? this.request[verb] : this.request];
3236
+ var keys = Object.keys((_a = rows[0]) !== null && _a !== void 0 ? _a : {});
3167
3237
  var params = this.useNamedParams ? {} : [];
3168
- var placeholders = [];
3169
- for (var _i = 0, keys_1 = keys; _i < keys_1.length; _i++) {
3170
- var key = keys_1[_i];
3171
- var value = body[key];
3172
- var trimmed = this.trimTablePrefix(table, key);
3173
- var qualified = "".concat(table, ".").concat(trimmed);
3174
- var placeholder = this.serializeUpdateValue(value, params, qualified);
3175
- placeholders.push(placeholder);
3238
+ var rowPlaceholders = [];
3239
+ for (var _i = 0, rows_1 = rows; _i < rows_1.length; _i++) {
3240
+ var row = rows_1[_i];
3241
+ var placeholders = [];
3242
+ for (var _c = 0, keys_1 = keys; _c < keys_1.length; _c++) {
3243
+ var key = keys_1[_c];
3244
+ var value = (_b = row[key]) !== null && _b !== void 0 ? _b : null;
3245
+ var trimmed = this.trimTablePrefix(table, key);
3246
+ var qualified = "".concat(table, ".").concat(trimmed);
3247
+ var placeholder = this.serializeUpdateValue(value, params, qualified);
3248
+ placeholders.push(placeholder);
3249
+ }
3250
+ rowPlaceholders.push("(".concat(placeholders.join(', '), ")"));
3176
3251
  }
3177
- var sql = "".concat(verb, " INTO `").concat(table, "` (\n ").concat(keys.map(function (k) { return "`".concat(_this.trimTablePrefix(table, k), "`"); }).join(', '), "\n ) VALUES (\n ").concat(placeholders.join(', '), "\n )");
3252
+ var sql = "".concat(verb, " INTO `").concat(table, "` (\n ").concat(keys.map(function (k) { return "`".concat(_this.trimTablePrefix(table, k), "`"); }).join(', '), "\n ) VALUES\n ").concat(rowPlaceholders.join(',\n '));
3178
3253
  if (C6C.UPDATE in this.request) {
3179
3254
  var updateData = this.request[C6C.UPDATE];
3180
3255
  if (!Array.isArray(updateData)) {
@@ -3494,12 +3569,18 @@ var SqlExecutor = /** @class */ (function (_super) {
3494
3569
  }
3495
3570
  SqlExecutor.prototype.execute = function () {
3496
3571
  return tslib.__awaiter(this, void 0, void 0, function () {
3497
- var TABLE_NAME, method, logContext, _a, rest, result, result, result;
3572
+ var TABLE_NAME, method, logContext, response, _a, rest, getResponse, restRows, result, result, result;
3498
3573
  return tslib.__generator(this, function (_b) {
3499
3574
  switch (_b.label) {
3500
3575
  case 0:
3501
3576
  TABLE_NAME = this.config.restModel.TABLE_NAME;
3502
3577
  method = this.config.requestMethod;
3578
+ return [4 /*yield*/, this.runLifecycleHooks("beforeProcessing", {
3579
+ config: this.config,
3580
+ request: this.request,
3581
+ })];
3582
+ case 1:
3583
+ _b.sent();
3503
3584
  // Normalize singular T-shaped requests into complex ORM shape (GET/PUT/DELETE)
3504
3585
  try {
3505
3586
  this.request = normalizeSingularRequest(method, this.request, this.config.restModel, undefined);
@@ -3513,45 +3594,54 @@ var SqlExecutor = /** @class */ (function (_super) {
3513
3594
  logWithLevel(exports.LogLevel.DEBUG, logContext, console.log, "[SQL EXECUTOR] \uD83E\uDDE9 Request:", this.request);
3514
3595
  _a = method;
3515
3596
  switch (_a) {
3516
- case 'GET': return [3 /*break*/, 1];
3517
- case 'POST': return [3 /*break*/, 3];
3518
- case 'PUT': return [3 /*break*/, 6];
3519
- case 'DELETE': return [3 /*break*/, 9];
3597
+ case 'GET': return [3 /*break*/, 2];
3598
+ case 'POST': return [3 /*break*/, 4];
3599
+ case 'PUT': return [3 /*break*/, 7];
3600
+ case 'DELETE': return [3 /*break*/, 10];
3520
3601
  }
3521
- return [3 /*break*/, 12];
3522
- case 1: return [4 /*yield*/, this.runQuery()];
3523
- case 2:
3602
+ return [3 /*break*/, 13];
3603
+ case 2: return [4 /*yield*/, this.runQuery()];
3604
+ case 3:
3524
3605
  rest = _b.sent();
3525
3606
  if (this.config.reactBootstrap) {
3607
+ getResponse = rest;
3608
+ restRows = Array.isArray(getResponse.rest)
3609
+ ? getResponse.rest
3610
+ : [getResponse.rest];
3526
3611
  this.config.reactBootstrap.updateRestfulObjectArrays({
3527
- dataOrCallback: rest.rest,
3612
+ dataOrCallback: restRows,
3528
3613
  stateKey: this.config.restModel.TABLE_NAME,
3529
3614
  uniqueObjectId: this.config.restModel.PRIMARY_SHORT,
3530
3615
  });
3531
3616
  }
3532
- return [2 /*return*/, rest];
3533
- case 3: return [4 /*yield*/, this.runQuery()];
3534
- case 4:
3617
+ response = rest;
3618
+ return [3 /*break*/, 14];
3619
+ case 4: return [4 /*yield*/, this.runQuery()];
3620
+ case 5:
3535
3621
  result = _b.sent();
3536
3622
  return [4 /*yield*/, this.broadcastWebsocketIfConfigured(result)];
3537
- case 5:
3623
+ case 6:
3538
3624
  _b.sent();
3539
- return [2 /*return*/, result];
3540
- case 6: return [4 /*yield*/, this.runQuery()];
3541
- case 7:
3625
+ response = result;
3626
+ return [3 /*break*/, 14];
3627
+ case 7: return [4 /*yield*/, this.runQuery()];
3628
+ case 8:
3542
3629
  result = _b.sent();
3543
3630
  return [4 /*yield*/, this.broadcastWebsocketIfConfigured(result)];
3544
- case 8:
3631
+ case 9:
3545
3632
  _b.sent();
3546
- return [2 /*return*/, result];
3547
- case 9: return [4 /*yield*/, this.runQuery()];
3548
- case 10:
3633
+ response = result;
3634
+ return [3 /*break*/, 14];
3635
+ case 10: return [4 /*yield*/, this.runQuery()];
3636
+ case 11:
3549
3637
  result = _b.sent();
3550
3638
  return [4 /*yield*/, this.broadcastWebsocketIfConfigured(result)];
3551
- case 11:
3639
+ case 12:
3552
3640
  _b.sent();
3553
- return [2 /*return*/, result];
3554
- case 12: throw new Error("Unsupported request method: ".concat(method));
3641
+ response = result;
3642
+ return [3 /*break*/, 14];
3643
+ case 13: throw new Error("Unsupported request method: ".concat(method));
3644
+ case 14: return [2 /*return*/, response];
3555
3645
  }
3556
3646
  });
3557
3647
  });
@@ -3848,65 +3938,182 @@ var SqlExecutor = /** @class */ (function (_super) {
3848
3938
  };
3849
3939
  SqlExecutor.prototype.runQuery = function () {
3850
3940
  return tslib.__awaiter(this, void 0, void 0, function () {
3851
- var TABLE_NAME, method, builder, QueryResult, logContext, formatted, toUnnamed, _a, sql, values;
3941
+ var method, tableName, logContext, cacheResults, cacheRequestData, requestArgumentsSerialized, cachedRequest, sqlExecution, queryPromise, cacheRequest, cacheResponse;
3852
3942
  var _this = this;
3853
- return tslib.__generator(this, function (_b) {
3854
- switch (_b.label) {
3943
+ var _a, _b;
3944
+ return tslib.__generator(this, function (_c) {
3945
+ switch (_c.label) {
3855
3946
  case 0:
3856
- TABLE_NAME = this.config.restModel.TABLE_NAME;
3857
3947
  method = this.config.requestMethod;
3858
- switch (method) {
3859
- case 'GET':
3860
- builder = new SelectQueryBuilder(this.config, this.request);
3861
- break;
3862
- case 'PUT':
3863
- builder = new UpdateQueryBuilder(this.config, this.request);
3864
- break;
3865
- case 'DELETE':
3866
- builder = new DeleteQueryBuilder(this.config, this.request);
3867
- break;
3868
- case 'POST':
3869
- builder = new PostQueryBuilder(this.config, this.request);
3870
- break;
3871
- default:
3872
- throw new Error("Unsupported query method: ".concat(method));
3873
- }
3874
- QueryResult = builder.build(TABLE_NAME);
3948
+ tableName = this.config.restModel.TABLE_NAME;
3875
3949
  logContext = getLogContext(this.config, this.request);
3876
- logWithLevel(exports.LogLevel.DEBUG, logContext, console.log, "[SQL EXECUTOR] \uD83E\uDDE0 Generated ".concat(method.toUpperCase(), " SQL:"), QueryResult);
3877
- formatted = this.formatSQLWithParams(QueryResult.sql, QueryResult.params);
3878
- logWithLevel(exports.LogLevel.DEBUG, logContext, console.log, "[SQL EXECUTOR] \uD83E\uDDE0 Formatted ".concat(method.toUpperCase(), " SQL:"), formatted);
3879
- toUnnamed = namedPlaceholders();
3880
- _a = toUnnamed(QueryResult.sql, QueryResult.params), sql = _a[0], values = _a[1];
3881
- return [4 /*yield*/, this.validateSqlAllowList(sql)];
3950
+ cacheResults = method === C6Constants.GET
3951
+ && !this.config.sqlAllowListPath
3952
+ && ((_a = this.request) === null || _a === void 0 ? void 0 : _a.cacheResults) !== false;
3953
+ cacheRequestData = cacheResults
3954
+ ? JSON.parse(JSON.stringify((_b = this.request) !== null && _b !== void 0 ? _b : {}))
3955
+ : undefined;
3956
+ requestArgumentsSerialized = cacheResults
3957
+ ? sortAndSerializeQueryObject(tableName, cacheRequestData !== null && cacheRequestData !== void 0 ? cacheRequestData : {})
3958
+ : undefined;
3959
+ if (!cacheResults) return [3 /*break*/, 2];
3960
+ cachedRequest = checkCache(method, tableName, cacheRequestData);
3961
+ if (!cachedRequest) return [3 /*break*/, 2];
3962
+ return [4 /*yield*/, cachedRequest];
3963
+ case 1: return [2 /*return*/, (_c.sent()).data];
3964
+ case 2:
3965
+ sqlExecution = this.buildSqlExecutionContext(method, tableName, logContext);
3966
+ queryPromise = this.withConnection(function (conn) { return tslib.__awaiter(_this, void 0, void 0, function () { return tslib.__generator(this, function (_a) {
3967
+ return [2 /*return*/, this.executeQueryWithLifecycle(conn, method, sqlExecution, logContext)];
3968
+ }); }); });
3969
+ if (!(!cacheResults || !cacheRequestData || !requestArgumentsSerialized)) return [3 /*break*/, 4];
3970
+ return [4 /*yield*/, queryPromise];
3971
+ case 3: return [2 /*return*/, _c.sent()];
3972
+ case 4:
3973
+ cacheRequest = queryPromise.then(function (data) {
3974
+ return _this.createCacheResponseEnvelope(method, tableName, data);
3975
+ });
3976
+ setCache(method, tableName, cacheRequestData, {
3977
+ requestArgumentsSerialized: requestArgumentsSerialized,
3978
+ request: cacheRequest,
3979
+ });
3980
+ return [4 /*yield*/, cacheRequest];
3981
+ case 5:
3982
+ cacheResponse = _c.sent();
3983
+ setCache(method, tableName, cacheRequestData, {
3984
+ requestArgumentsSerialized: requestArgumentsSerialized,
3985
+ request: cacheRequest,
3986
+ response: cacheResponse,
3987
+ final: true,
3988
+ });
3989
+ return [2 /*return*/, cacheResponse.data];
3990
+ }
3991
+ });
3992
+ });
3993
+ };
3994
+ SqlExecutor.prototype.getQueryBuilder = function (method) {
3995
+ switch (method) {
3996
+ case C6Constants.GET:
3997
+ return new SelectQueryBuilder(this.config, this.request);
3998
+ case C6Constants.PUT:
3999
+ return new UpdateQueryBuilder(this.config, this.request);
4000
+ case C6Constants.DELETE:
4001
+ return new DeleteQueryBuilder(this.config, this.request);
4002
+ case C6Constants.POST:
4003
+ return new PostQueryBuilder(this.config, this.request);
4004
+ default:
4005
+ throw new Error("Unsupported query method: ".concat(method));
4006
+ }
4007
+ };
4008
+ SqlExecutor.prototype.buildSqlExecutionContext = function (method, tableName, logContext) {
4009
+ var builder = this.getQueryBuilder(method);
4010
+ var queryResult = builder.build(tableName);
4011
+ logWithLevel(exports.LogLevel.DEBUG, logContext, console.log, "[SQL EXECUTOR] \uD83E\uDDE0 Generated ".concat(method.toUpperCase(), " SQL:"), queryResult);
4012
+ var formatted = this.formatSQLWithParams(queryResult.sql, queryResult.params);
4013
+ logWithLevel(exports.LogLevel.DEBUG, logContext, console.log, "[SQL EXECUTOR] \uD83E\uDDE0 Formatted ".concat(method.toUpperCase(), " SQL:"), formatted);
4014
+ var toUnnamed = namedPlaceholders();
4015
+ var _a = toUnnamed(queryResult.sql, queryResult.params), sql = _a[0], values = _a[1];
4016
+ return { sql: sql, values: values };
4017
+ };
4018
+ SqlExecutor.prototype.createResponseFromQueryResult = function (method, result, sqlExecution, logContext) {
4019
+ if (method === C6Constants.GET) {
4020
+ return {
4021
+ rest: result.map(this.serialize),
4022
+ sql: { sql: sqlExecution.sql, values: sqlExecution.values },
4023
+ };
4024
+ }
4025
+ logWithLevel(exports.LogLevel.DEBUG, logContext, console.log, "[SQL EXECUTOR] \u270F\uFE0F Rows affected:", result.affectedRows);
4026
+ return {
4027
+ affected: result.affectedRows,
4028
+ insertId: result.insertId,
4029
+ rest: [],
4030
+ sql: { sql: sqlExecution.sql, values: sqlExecution.values },
4031
+ };
4032
+ };
4033
+ SqlExecutor.prototype.createLifecycleHookResponse = function (response) {
4034
+ var data = Object.assign({ success: true }, response);
4035
+ return { data: data };
4036
+ };
4037
+ SqlExecutor.prototype.createCacheResponseEnvelope = function (method, tableName, data) {
4038
+ return {
4039
+ data: data,
4040
+ config: {
4041
+ method: method.toLowerCase(),
4042
+ url: "/rest/".concat(tableName),
4043
+ },
4044
+ };
4045
+ };
4046
+ SqlExecutor.prototype.executeQueryWithLifecycle = function (conn, method, sqlExecution, logContext) {
4047
+ return tslib.__awaiter(this, void 0, void 0, function () {
4048
+ var useTransaction, committed, result, response, hookResponse, err_1, rollbackErr_1;
4049
+ return tslib.__generator(this, function (_a) {
4050
+ switch (_a.label) {
4051
+ case 0:
4052
+ useTransaction = method !== C6Constants.GET;
4053
+ committed = false;
4054
+ _a.label = 1;
3882
4055
  case 1:
3883
- _b.sent();
3884
- return [4 /*yield*/, this.withConnection(function (conn) { return tslib.__awaiter(_this, void 0, void 0, function () {
3885
- var result;
3886
- return tslib.__generator(this, function (_a) {
3887
- switch (_a.label) {
3888
- case 0: return [4 /*yield*/, conn.query(sql, values)];
3889
- case 1:
3890
- result = (_a.sent())[0];
3891
- if (method === 'GET') {
3892
- return [2 /*return*/, {
3893
- rest: result.map(this.serialize),
3894
- sql: { sql: sql, values: values }
3895
- }];
3896
- }
3897
- else {
3898
- logWithLevel(exports.LogLevel.DEBUG, logContext, console.log, "[SQL EXECUTOR] \u270F\uFE0F Rows affected:", result.affectedRows);
3899
- return [2 /*return*/, {
3900
- affected: result.affectedRows,
3901
- insertId: result.insertId,
3902
- rest: [], // TODO - remove rest empty array from non-GET responses?
3903
- sql: { sql: sql, values: values }
3904
- }];
3905
- }
3906
- }
3907
- });
3908
- }); })];
3909
- case 2: return [2 /*return*/, _b.sent()];
4056
+ _a.trys.push([1, 11, , 16]);
4057
+ if (!useTransaction) return [3 /*break*/, 3];
4058
+ logWithLevel(exports.LogLevel.DEBUG, logContext, console.log, "[SQL EXECUTOR] \uD83E\uDDFE Beginning transaction");
4059
+ return [4 /*yield*/, conn.beginTransaction()];
4060
+ case 2:
4061
+ _a.sent();
4062
+ _a.label = 3;
4063
+ case 3: return [4 /*yield*/, this.validateSqlAllowList(sqlExecution.sql)];
4064
+ case 4:
4065
+ _a.sent();
4066
+ return [4 /*yield*/, this.runLifecycleHooks("beforeExecution", {
4067
+ config: this.config,
4068
+ request: this.request,
4069
+ sqlExecution: sqlExecution,
4070
+ })];
4071
+ case 5:
4072
+ _a.sent();
4073
+ return [4 /*yield*/, conn.query(sqlExecution.sql, sqlExecution.values)];
4074
+ case 6:
4075
+ result = (_a.sent())[0];
4076
+ response = this.createResponseFromQueryResult(method, result, sqlExecution, logContext);
4077
+ hookResponse = this.createLifecycleHookResponse(response);
4078
+ return [4 /*yield*/, this.runLifecycleHooks("afterExecution", {
4079
+ config: this.config,
4080
+ request: this.request,
4081
+ response: hookResponse,
4082
+ })];
4083
+ case 7:
4084
+ _a.sent();
4085
+ if (!useTransaction) return [3 /*break*/, 9];
4086
+ return [4 /*yield*/, conn.commit()];
4087
+ case 8:
4088
+ _a.sent();
4089
+ committed = true;
4090
+ logWithLevel(exports.LogLevel.DEBUG, logContext, console.log, "[SQL EXECUTOR] \uD83E\uDDFE Transaction committed");
4091
+ _a.label = 9;
4092
+ case 9: return [4 /*yield*/, this.runLifecycleHooks("afterCommit", {
4093
+ config: this.config,
4094
+ request: this.request,
4095
+ response: hookResponse,
4096
+ })];
4097
+ case 10:
4098
+ _a.sent();
4099
+ return [2 /*return*/, response];
4100
+ case 11:
4101
+ err_1 = _a.sent();
4102
+ if (!(useTransaction && !committed)) return [3 /*break*/, 15];
4103
+ _a.label = 12;
4104
+ case 12:
4105
+ _a.trys.push([12, 14, , 15]);
4106
+ return [4 /*yield*/, conn.rollback()];
4107
+ case 13:
4108
+ _a.sent();
4109
+ logWithLevel(exports.LogLevel.WARN, logContext, console.warn, "[SQL EXECUTOR] \uD83E\uDDFE Transaction rolled back");
4110
+ return [3 /*break*/, 15];
4111
+ case 14:
4112
+ rollbackErr_1 = _a.sent();
4113
+ logWithLevel(exports.LogLevel.ERROR, logContext, console.error, "[SQL EXECUTOR] Rollback failed", rollbackErr_1);
4114
+ return [3 /*break*/, 15];
4115
+ case 15: throw err_1;
4116
+ case 16: return [2 /*return*/];
3910
4117
  }
3911
4118
  });
3912
4119
  });
@@ -3941,18 +4148,23 @@ var SqlExecutor$1 = /*#__PURE__*/Object.freeze({
3941
4148
  SqlExecutor: SqlExecutor
3942
4149
  });
3943
4150
 
4151
+ function restExpressRequest(routerConfig) {
4152
+ var router = routerConfig.router, _a = routerConfig.routePath, routePath = _a === void 0 ? "/rest/:table{/:primary}" : _a, handlerConfig = tslib.__rest(routerConfig, ["router", "routePath"]);
4153
+ router.all(routePath, ExpressHandler(handlerConfig));
4154
+ }
3944
4155
  // TODO - WE MUST make this a generic - optional, but helpful
3945
4156
  // note sure how it would help anyone actually...
3946
- function ExpressHandler(_a) {
4157
+ function ExpressHandler(configX) {
3947
4158
  var _this = this;
3948
- var C6 = _a.C6, mysqlPool = _a.mysqlPool, sqlAllowListPath = _a.sqlAllowListPath, websocketBroadcast = _a.websocketBroadcast;
3949
- return function (req, res, next) { return tslib.__awaiter(_this, void 0, void 0, function () {
3950
- var incomingMethod, table, primary, methodOverrideRaw, methodOverride, treatAsGet, method, payload, restModel, primaryKeys_1, primaryShortKeys_1, columnMap_1, resolveShortKey_1, hasPrimaryKeyValues, primaryKeyName, response, err_1;
4159
+ return function (req, res) { return tslib.__awaiter(_this, void 0, void 0, function () {
4160
+ var config, C6, incomingMethod, table, primary, methodOverrideRaw, methodOverride, treatAsGet, method, payload, normalized, restModel, primaryKeys_1, primaryShortKeys_1, columnMap_1, resolveShortKey_1, hasPrimaryKeyValues, primaryKeyName, response, err_1, message;
3951
4161
  var _a, _b, _c, _d, _e, _f, _g;
3952
4162
  return tslib.__generator(this, function (_h) {
3953
4163
  switch (_h.label) {
3954
4164
  case 0:
3955
4165
  _h.trys.push([0, 2, , 3]);
4166
+ config = typeof configX === "function" ? configX() : configX;
4167
+ C6 = config.C6;
3956
4168
  incomingMethod = req.method.toUpperCase();
3957
4169
  table = req.params.table;
3958
4170
  primary = req.params.primary;
@@ -3961,12 +4173,21 @@ function ExpressHandler(_a) {
3961
4173
  treatAsGet = incomingMethod === 'POST' && methodOverride === 'GET';
3962
4174
  method = treatAsGet ? 'GET' : incomingMethod;
3963
4175
  payload = treatAsGet ? tslib.__assign({}, req.body) : (method === 'GET' ? req.query : req.body);
4176
+ // Query strings are text; coerce known boolean controls.
4177
+ if (typeof (payload === null || payload === void 0 ? void 0 : payload.cacheResults) === "string") {
4178
+ normalized = payload.cacheResults.toLowerCase();
4179
+ if (normalized === "false")
4180
+ payload.cacheResults = false;
4181
+ if (normalized === "true")
4182
+ payload.cacheResults = true;
4183
+ }
3964
4184
  // Remove transport-only METHOD flag so it never leaks into ORM parsing
3965
4185
  if (treatAsGet && 'METHOD' in payload) {
3966
4186
  try {
3967
4187
  delete payload.METHOD;
3968
4188
  }
3969
- catch ( /* noop */_j) { /* noop */ }
4189
+ catch ( /* noop */_j) { /* noop */
4190
+ }
3970
4191
  }
3971
4192
  // Warn for unsupported overrides but continue normally
3972
4193
  if (incomingMethod !== 'GET' && methodOverride && methodOverride !== 'GET') {
@@ -4028,22 +4249,16 @@ function ExpressHandler(_a) {
4028
4249
  (_g = payload[primaryKeyName]) !== null && _g !== void 0 ? _g : primary;
4029
4250
  }
4030
4251
  }
4031
- return [4 /*yield*/, restRequest({
4032
- C6: C6,
4033
- mysqlPool: mysqlPool,
4034
- sqlAllowListPath: sqlAllowListPath,
4035
- websocketBroadcast: websocketBroadcast,
4036
- requestMethod: method,
4037
- restModel: C6.TABLES[table]
4038
- })(payload)];
4252
+ return [4 /*yield*/, restRequest(tslib.__assign(tslib.__assign({}, config), { requestMethod: method, restModel: C6.TABLES[table] }))(payload)];
4039
4253
  case 1:
4040
4254
  response = _h.sent();
4041
4255
  res.status(200).json(tslib.__assign({ success: true }, response));
4042
4256
  return [3 /*break*/, 3];
4043
4257
  case 2:
4044
4258
  err_1 = _h.sent();
4045
- res.status(500).json({ success: false, error: err_1 });
4046
- next(err_1);
4259
+ message = err_1 instanceof Error ? err_1.message : String(err_1);
4260
+ logWithLevel(exports.LogLevel.ERROR, undefined, console.error, message);
4261
+ res.status(500).json({ success: false, error: message });
4047
4262
  return [3 /*break*/, 3];
4048
4263
  case 3: return [2 /*return*/];
4049
4264
  }
@@ -4221,6 +4436,7 @@ exports.removeInvalidKeys = removeInvalidKeys;
4221
4436
  exports.removePrefixIfExists = removePrefixIfExists;
4222
4437
  exports.resolveDerivedTable = resolveDerivedTable;
4223
4438
  exports.resolveLogLevel = resolveLogLevel;
4439
+ exports.restExpressRequest = restExpressRequest;
4224
4440
  exports.restOrm = restOrm;
4225
4441
  exports.restRequest = restRequest;
4226
4442
  exports.setCache = setCache;