@carbonorm/carbonnode 6.0.18 → 6.0.20

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 (64) hide show
  1. package/dist/executors/SqlExecutor.d.ts +7 -0
  2. package/dist/index.cjs.js +230 -20
  3. package/dist/index.cjs.js.map +1 -1
  4. package/dist/index.esm.js +230 -20
  5. package/dist/index.esm.js.map +1 -1
  6. package/dist/types/ormInterfaces.d.ts +1 -0
  7. package/dist/utils/cacheManager.d.ts +3 -2
  8. package/dist/utils/logSql.d.ts +1 -1
  9. package/package.json +1 -1
  10. package/src/__tests__/cacheManager.test.ts +55 -1
  11. package/src/__tests__/logSql.test.ts +16 -0
  12. package/src/__tests__/sakila-db/C6.js +1 -1
  13. package/src/__tests__/sakila-db/C6.mysqldump.json +1 -1
  14. package/src/__tests__/sakila-db/C6.mysqldump.sql +1 -1
  15. package/src/__tests__/sakila-db/C6.ts +1 -1
  16. package/src/__tests__/sakila-db/sqlResponses/C6.actor.post.json +11 -4
  17. package/src/__tests__/sakila-db/sqlResponses/C6.actor.post.latest.json +3 -3
  18. package/src/__tests__/sakila-db/sqlResponses/C6.actor.put.json +1 -1
  19. package/src/__tests__/sakila-db/sqlResponses/C6.actor.put.lookup.json +3 -3
  20. package/src/__tests__/sakila-db/sqlResponses/C6.address.post.json +18 -6
  21. package/src/__tests__/sakila-db/sqlResponses/C6.address.post.latest.json +5 -5
  22. package/src/__tests__/sakila-db/sqlResponses/C6.address.put.json +1 -1
  23. package/src/__tests__/sakila-db/sqlResponses/C6.address.put.lookup.json +5 -5
  24. package/src/__tests__/sakila-db/sqlResponses/C6.category.post.json +9 -3
  25. package/src/__tests__/sakila-db/sqlResponses/C6.category.post.latest.json +2 -2
  26. package/src/__tests__/sakila-db/sqlResponses/C6.category.put.json +1 -1
  27. package/src/__tests__/sakila-db/sqlResponses/C6.category.put.lookup.json +2 -2
  28. package/src/__tests__/sakila-db/sqlResponses/C6.city.post.json +10 -3
  29. package/src/__tests__/sakila-db/sqlResponses/C6.city.post.latest.json +2 -2
  30. package/src/__tests__/sakila-db/sqlResponses/C6.city.put.json +1 -1
  31. package/src/__tests__/sakila-db/sqlResponses/C6.city.put.lookup.json +2 -2
  32. package/src/__tests__/sakila-db/sqlResponses/C6.country.post.json +9 -3
  33. package/src/__tests__/sakila-db/sqlResponses/C6.country.post.latest.json +2 -2
  34. package/src/__tests__/sakila-db/sqlResponses/C6.country.put.json +1 -1
  35. package/src/__tests__/sakila-db/sqlResponses/C6.country.put.lookup.json +2 -2
  36. package/src/__tests__/sakila-db/sqlResponses/C6.customer.post.json +18 -6
  37. package/src/__tests__/sakila-db/sqlResponses/C6.customer.post.latest.json +5 -5
  38. package/src/__tests__/sakila-db/sqlResponses/C6.customer.put.json +1 -1
  39. package/src/__tests__/sakila-db/sqlResponses/C6.customer.put.lookup.json +5 -5
  40. package/src/__tests__/sakila-db/sqlResponses/C6.film.post.json +18 -3
  41. package/src/__tests__/sakila-db/sqlResponses/C6.film.post.latest.json +2 -2
  42. package/src/__tests__/sakila-db/sqlResponses/C6.film.put.json +1 -1
  43. package/src/__tests__/sakila-db/sqlResponses/C6.film.put.lookup.json +2 -2
  44. package/src/__tests__/sakila-db/sqlResponses/C6.inventory.post.json +9 -2
  45. package/src/__tests__/sakila-db/sqlResponses/C6.inventory.post.latest.json +1 -1
  46. package/src/__tests__/sakila-db/sqlResponses/C6.inventory.put.json +1 -1
  47. package/src/__tests__/sakila-db/sqlResponses/C6.inventory.put.lookup.json +1 -1
  48. package/src/__tests__/sakila-db/sqlResponses/C6.language.post.json +9 -3
  49. package/src/__tests__/sakila-db/sqlResponses/C6.language.post.latest.json +2 -2
  50. package/src/__tests__/sakila-db/sqlResponses/C6.language.put.json +1 -1
  51. package/src/__tests__/sakila-db/sqlResponses/C6.language.put.lookup.json +2 -2
  52. package/src/__tests__/sakila-db/sqlResponses/C6.payment.post.json +13 -3
  53. package/src/__tests__/sakila-db/sqlResponses/C6.payment.post.latest.json +2 -2
  54. package/src/__tests__/sakila-db/sqlResponses/C6.payment.put.lookup.json +2 -2
  55. package/src/__tests__/sakila-db/sqlResponses/C6.rental.post.json +14 -4
  56. package/src/__tests__/sakila-db/sqlResponses/C6.rental.post.latest.json +3 -3
  57. package/src/__tests__/sakila-db/sqlResponses/C6.rental.put.json +1 -1
  58. package/src/__tests__/sakila-db/sqlResponses/C6.rental.put.lookup.json +3 -3
  59. package/src/__tests__/sqlExecutorPostUuid.test.ts +185 -0
  60. package/src/executors/HttpExecutor.ts +33 -5
  61. package/src/executors/SqlExecutor.ts +207 -3
  62. package/src/types/ormInterfaces.ts +1 -0
  63. package/src/utils/cacheManager.ts +22 -5
  64. package/src/utils/logSql.ts +3 -1
@@ -1,7 +1,17 @@
1
1
  {
2
2
  "affected": 1,
3
3
  "insertId": 16050,
4
- "rest": [],
4
+ "rest": [
5
+ {
6
+ "customer_id": 2,
7
+ "staff_id": 1,
8
+ "rental_id": 1,
9
+ "amount": 1,
10
+ "payment_date": "2026-02-13 17:56:13",
11
+ "last_update": "2026-02-13 17:56:13",
12
+ "payment_id": 16050
13
+ }
14
+ ],
5
15
  "sql": {
6
16
  "sql": "INSERT INTO `payment` (\n `customer_id`, `staff_id`, `rental_id`, `amount`, `payment_date`, `last_update`\n ) VALUES\n (?, ?, ?, ?, ?, ?)",
7
17
  "values": [
@@ -9,8 +19,8 @@
9
19
  1,
10
20
  1,
11
21
  1,
12
- "2026-02-12 22:07:32",
13
- "2026-02-12 22:07:32"
22
+ "2026-02-13 17:56:13",
23
+ "2026-02-13 17:56:13"
14
24
  ]
15
25
  }
16
26
  }
@@ -6,8 +6,8 @@
6
6
  "staff_id": 1,
7
7
  "rental_id": 1,
8
8
  "amount": "1.00",
9
- "payment_date": "2026-02-12T22:07:32.000Z",
10
- "last_update": "2026-02-12T22:07:32.000Z"
9
+ "payment_date": "2026-02-13T17:56:13.000Z",
10
+ "last_update": "2026-02-13T17:56:13.000Z"
11
11
  }
12
12
  ],
13
13
  "sql": {
@@ -6,8 +6,8 @@
6
6
  "staff_id": 1,
7
7
  "rental_id": 1,
8
8
  "amount": "1.00",
9
- "payment_date": "2026-02-12T22:07:32.000Z",
10
- "last_update": "2026-02-12T22:07:32.000Z"
9
+ "payment_date": "2026-02-13T17:56:13.000Z",
10
+ "last_update": "2026-02-13T17:56:13.000Z"
11
11
  }
12
12
  ],
13
13
  "sql": {
@@ -1,16 +1,26 @@
1
1
  {
2
2
  "affected": 1,
3
3
  "insertId": 16050,
4
- "rest": [],
4
+ "rest": [
5
+ {
6
+ "rental_date": "2026-02-13 17:56:13",
7
+ "inventory_id": 1,
8
+ "customer_id": 1,
9
+ "return_date": "2026-02-13 17:56:13",
10
+ "staff_id": 1,
11
+ "last_update": "2026-02-13 17:56:13",
12
+ "rental_id": 16050
13
+ }
14
+ ],
5
15
  "sql": {
6
16
  "sql": "INSERT INTO `rental` (\n `rental_date`, `inventory_id`, `customer_id`, `return_date`, `staff_id`, `last_update`\n ) VALUES\n (?, ?, ?, ?, ?, ?)",
7
17
  "values": [
8
- "2026-02-12 22:07:32",
18
+ "2026-02-13 17:56:13",
9
19
  1,
10
20
  1,
11
- "2026-02-12 22:07:32",
21
+ "2026-02-13 17:56:13",
12
22
  1,
13
- "2026-02-12 22:07:32"
23
+ "2026-02-13 17:56:13"
14
24
  ]
15
25
  }
16
26
  }
@@ -2,12 +2,12 @@
2
2
  "rest": [
3
3
  {
4
4
  "rental_id": 16050,
5
- "rental_date": "2026-02-12T22:07:32.000Z",
5
+ "rental_date": "2026-02-13T17:56:13.000Z",
6
6
  "inventory_id": 1,
7
7
  "customer_id": 1,
8
- "return_date": "2026-02-12T22:07:32.000Z",
8
+ "return_date": "2026-02-13T17:56:13.000Z",
9
9
  "staff_id": 1,
10
- "last_update": "2026-02-12T22:07:32.000Z"
10
+ "last_update": "2026-02-13T17:56:13.000Z"
11
11
  }
12
12
  ],
13
13
  "sql": {
@@ -5,7 +5,7 @@
5
5
  "sql": {
6
6
  "sql": "UPDATE `rental` SET `rental_date` = ? WHERE (rental.rental_id) = ?",
7
7
  "values": [
8
- "2026-02-12 22:07:32",
8
+ "2026-02-13 17:56:13",
9
9
  16050
10
10
  ]
11
11
  }
@@ -2,12 +2,12 @@
2
2
  "rest": [
3
3
  {
4
4
  "rental_id": 16050,
5
- "rental_date": "2026-02-12T22:07:32.000Z",
5
+ "rental_date": "2026-02-13T17:56:13.000Z",
6
6
  "inventory_id": 1,
7
7
  "customer_id": 1,
8
- "return_date": "2026-02-12T22:07:32.000Z",
8
+ "return_date": "2026-02-13T17:56:13.000Z",
9
9
  "staff_id": 1,
10
- "last_update": "2026-02-12T22:07:32.000Z"
10
+ "last_update": "2026-02-13T17:56:13.000Z"
11
11
  }
12
12
  ],
13
13
  "sql": {
@@ -0,0 +1,185 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+ import { SqlExecutor } from "../executors/SqlExecutor";
3
+
4
+ function buildLifecycleHooks() {
5
+ return { GET: {}, POST: {}, PUT: {}, DELETE: {} } as any;
6
+ }
7
+
8
+ function buildUuidPrimaryConfig(conn: any) {
9
+ const restModel: any = {
10
+ TABLE_NAME: "report_dashboards",
11
+ PRIMARY: ["report_dashboards.dashboard_id"],
12
+ PRIMARY_SHORT: ["dashboard_id"],
13
+ COLUMNS: {
14
+ "report_dashboards.dashboard_id": "dashboard_id",
15
+ "report_dashboards.title": "title",
16
+ "report_dashboards.created_by": "created_by",
17
+ },
18
+ TYPE_VALIDATION: {
19
+ "report_dashboards.dashboard_id": {
20
+ MYSQL_TYPE: "binary",
21
+ MAX_LENGTH: "16",
22
+ AUTO_INCREMENT: false,
23
+ SKIP_COLUMN_IN_POST: false,
24
+ },
25
+ "report_dashboards.title": {
26
+ MYSQL_TYPE: "varchar",
27
+ MAX_LENGTH: "120",
28
+ AUTO_INCREMENT: false,
29
+ SKIP_COLUMN_IN_POST: false,
30
+ },
31
+ "report_dashboards.created_by": {
32
+ MYSQL_TYPE: "varchar",
33
+ MAX_LENGTH: "255",
34
+ AUTO_INCREMENT: false,
35
+ SKIP_COLUMN_IN_POST: false,
36
+ },
37
+ },
38
+ LIFECYCLE_HOOKS: buildLifecycleHooks(),
39
+ };
40
+
41
+ return {
42
+ requestMethod: "POST",
43
+ mysqlPool: {
44
+ getConnection: vi.fn(async () => conn),
45
+ },
46
+ C6: {
47
+ PREFIX: "",
48
+ TABLES: {
49
+ report_dashboards: restModel,
50
+ },
51
+ },
52
+ restModel,
53
+ } as any;
54
+ }
55
+
56
+ function buildAutoIncrementConfig(conn: any) {
57
+ const restModel: any = {
58
+ TABLE_NAME: "widgets",
59
+ PRIMARY: ["widgets.widget_id"],
60
+ PRIMARY_SHORT: ["widget_id"],
61
+ COLUMNS: {
62
+ "widgets.widget_id": "widget_id",
63
+ "widgets.name": "name",
64
+ },
65
+ TYPE_VALIDATION: {
66
+ "widgets.widget_id": {
67
+ MYSQL_TYPE: "int",
68
+ MAX_LENGTH: "11",
69
+ AUTO_INCREMENT: true,
70
+ SKIP_COLUMN_IN_POST: false,
71
+ },
72
+ "widgets.name": {
73
+ MYSQL_TYPE: "varchar",
74
+ MAX_LENGTH: "120",
75
+ AUTO_INCREMENT: false,
76
+ SKIP_COLUMN_IN_POST: false,
77
+ },
78
+ },
79
+ LIFECYCLE_HOOKS: buildLifecycleHooks(),
80
+ };
81
+
82
+ return {
83
+ requestMethod: "POST",
84
+ mysqlPool: {
85
+ getConnection: vi.fn(async () => conn),
86
+ },
87
+ C6: {
88
+ PREFIX: "",
89
+ TABLES: {
90
+ widgets: restModel,
91
+ },
92
+ },
93
+ restModel,
94
+ } as any;
95
+ }
96
+
97
+ function buildWriteConn(affectedRows = 1, insertId = 0) {
98
+ return {
99
+ beginTransaction: vi.fn(async () => undefined),
100
+ query: vi.fn(async () => [{ affectedRows, insertId }, []]),
101
+ commit: vi.fn(async () => undefined),
102
+ rollback: vi.fn(async () => undefined),
103
+ release: vi.fn(),
104
+ };
105
+ }
106
+
107
+ function expectUuidV7Hex(value: unknown) {
108
+ expect(typeof value).toBe("string");
109
+ const hex = String(value).toUpperCase();
110
+ expect(hex).toMatch(/^[0-9A-F]{32}$/);
111
+ expect(hex[12]).toBe("7");
112
+ expect(hex[16]).toMatch(/[89AB]/);
113
+ }
114
+
115
+ describe("SqlExecutor POST UUID synthesis", () => {
116
+ it("generates missing UUID primary keys for POST and populates rest payload", async () => {
117
+ const conn = buildWriteConn(1, 0);
118
+ const config = buildUuidPrimaryConfig(conn);
119
+ const request: any = {
120
+ title: "Board One",
121
+ created_by: "user-1",
122
+ };
123
+
124
+ const executor = new SqlExecutor<any>(config, request);
125
+ const result: any = await executor.execute();
126
+
127
+ expect(Array.isArray(result.rest)).toBe(true);
128
+ expect(result.rest).toHaveLength(1);
129
+ expect(result.rest[0]).toMatchObject({
130
+ title: "Board One",
131
+ created_by: "user-1",
132
+ });
133
+ expectUuidV7Hex(result.rest[0].dashboard_id);
134
+
135
+ const [sql, values] = conn.query.mock.calls[0];
136
+ expect(sql).toContain("INSERT INTO `report_dashboards`");
137
+ const uuidBuffers = (values as any[]).filter((value) => Buffer.isBuffer(value));
138
+ expect(uuidBuffers).toHaveLength(1);
139
+ expect(uuidBuffers[0]).toHaveLength(16);
140
+ });
141
+
142
+ it("generates UUIDs for each multi-row insert payload row", async () => {
143
+ const conn = buildWriteConn(2, 0);
144
+ const config = buildUuidPrimaryConfig(conn);
145
+ const request: any = {
146
+ dataInsertMultipleRows: [
147
+ { title: "One", created_by: "user-1" },
148
+ { title: "Two", created_by: "user-1" },
149
+ ],
150
+ };
151
+
152
+ const executor = new SqlExecutor<any>(config, request);
153
+ const result: any = await executor.execute();
154
+
155
+ expect(Array.isArray(result.rest)).toBe(true);
156
+ expect(result.rest).toHaveLength(2);
157
+ expectUuidV7Hex(result.rest[0].dashboard_id);
158
+ expectUuidV7Hex(result.rest[1].dashboard_id);
159
+ expect(result.rest[0].dashboard_id).not.toBe(result.rest[1].dashboard_id);
160
+
161
+ const [, values] = conn.query.mock.calls[0];
162
+ const uuidBuffers = (values as any[]).filter((value) => Buffer.isBuffer(value));
163
+ expect(uuidBuffers).toHaveLength(2);
164
+ expect(uuidBuffers[0]).toHaveLength(16);
165
+ expect(uuidBuffers[1]).toHaveLength(16);
166
+ });
167
+
168
+ it("fills response rest primary key from insertId for autoincrement inserts", async () => {
169
+ const conn = buildWriteConn(1, 42);
170
+ const config = buildAutoIncrementConfig(conn);
171
+ const request: any = {
172
+ name: "Auto Row",
173
+ };
174
+
175
+ const executor = new SqlExecutor<any>(config, request);
176
+ const result: any = await executor.execute();
177
+
178
+ expect(Array.isArray(result.rest)).toBe(true);
179
+ expect(result.rest).toHaveLength(1);
180
+ expect(result.rest[0]).toMatchObject({
181
+ widget_id: 42,
182
+ name: "Auto Row",
183
+ });
184
+ });
185
+ });
@@ -14,6 +14,7 @@ import {
14
14
  } from "../types/ormInterfaces";
15
15
  import {removeInvalidKeys, removePrefixIfExists, TestRestfulResponse} from "../utils/apiHelpers";
16
16
  import {checkCache, evictCacheEntry, setCache, userCustomClearCache} from "../utils/cacheManager";
17
+ import type { SqlAllowListStatus } from "../utils/logSql";
17
18
  import {sortAndSerializeQueryObject} from "../utils/sortAndSerializeQueryObject";
18
19
  import {notifyToast} from "../utils/toastRuntime";
19
20
  import {Executor} from "./Executor";
@@ -81,11 +82,18 @@ export class HttpExecutor<
81
82
  ) {
82
83
  type RT = G['RestTableInterface'];
83
84
  type PK = G['PrimaryKey'];
85
+ const responseRestRaw = (response.data as any)?.rest;
86
+ const responseRows: Record<string, any>[] = Array.isArray(responseRestRaw)
87
+ ? responseRestRaw
88
+ : (responseRestRaw ? [responseRestRaw] : []);
84
89
 
85
90
  if (this.config.restModel.PRIMARY_SHORT.length === 1) {
86
91
  const pk = this.config.restModel.PRIMARY_SHORT[0] as PK;
87
92
  try {
88
- (request as unknown as Record<PK, RT[PK]>)[pk] = (response.data as any)?.created as RT[PK];
93
+ const created = (response.data as any)?.created ?? responseRows[0]?.[pk as string];
94
+ if (created !== undefined) {
95
+ (request as unknown as Record<PK, RT[PK]>)[pk] = created as RT[PK];
96
+ }
89
97
  } catch {/* best-effort */}
90
98
  } else if (isLocal()) {
91
99
  logWithLevel(
@@ -103,13 +111,13 @@ export class HttpExecutor<
103
111
  const normalizedRow = this.stripTableNameFromKeys<RT>(row as Partial<RT>);
104
112
  return removeInvalidKeys<RT>({
105
113
  ...normalizedRow,
106
- ...(index === 0 ? (response?.data as any)?.rest : {}),
114
+ ...(responseRows[index] ?? {}),
107
115
  }, this.config.C6.TABLES)
108
116
  })
109
117
  : [
110
118
  removeInvalidKeys<RT>({
111
119
  ...this.stripTableNameFromKeys<RT>(request as unknown as Partial<RT>),
112
- ...(response?.data as any)?.rest,
120
+ ...(responseRows[0] ?? {}),
113
121
  }, this.config.C6.TABLES)
114
122
  ],
115
123
  stateKey: this.config.restModel.TABLE_NAME,
@@ -253,10 +261,19 @@ export class HttpExecutor<
253
261
  G['CustomAndRequiredFields'],
254
262
  G['RequestTableOverrides']
255
263
  >;
264
+ const cacheAllowListStatus: SqlAllowListStatus = this.config.sqlAllowListPath
265
+ ? "allowed"
266
+ : "not verified";
256
267
 
257
268
  const evictFromCache =
258
269
  requestMethod === GET && cacheResults
259
- ? () => evictCacheEntry(requestMethod, tableName, cacheRequestData)
270
+ ? () => evictCacheEntry(
271
+ requestMethod,
272
+ tableName,
273
+ cacheRequestData,
274
+ logContext,
275
+ cacheAllowListStatus,
276
+ )
260
277
  : undefined;
261
278
 
262
279
  // literally impossible for query to be undefined or null here but the editor is too busy licking windows to understand that
@@ -265,7 +282,13 @@ export class HttpExecutor<
265
282
  let cachedRequest: Promise<{ data: ResponseDataType }> | false = false;
266
283
 
267
284
  if (cacheResults) {
268
- cachedRequest = checkCache<ResponseDataType>(requestMethod, tableName, cacheRequestData, logContext);
285
+ cachedRequest = checkCache<ResponseDataType>(
286
+ requestMethod,
287
+ tableName,
288
+ cacheRequestData,
289
+ logContext,
290
+ cacheAllowListStatus,
291
+ );
269
292
  }
270
293
 
271
294
  if (cachedRequest) {
@@ -464,6 +487,7 @@ export class HttpExecutor<
464
487
  setCache<ResponseDataType>(requestMethod, tableName, cacheRequestData, {
465
488
  requestArgumentsSerialized: querySerialized,
466
489
  request: axiosActiveRequest,
490
+ allowListStatus: cacheAllowListStatus,
467
491
  });
468
492
  }
469
493
 
@@ -480,6 +504,7 @@ export class HttpExecutor<
480
504
  setCache<ResponseDataType>(requestMethod, tableName, cacheRequestData, {
481
505
  requestArgumentsSerialized: querySerialized,
482
506
  request: axiosActiveRequest,
507
+ allowListStatus: cacheAllowListStatus,
483
508
  response,
484
509
  final: true,
485
510
  });
@@ -503,6 +528,7 @@ export class HttpExecutor<
503
528
  setCache<ResponseDataType>(requestMethod, tableName, cacheRequestData, {
504
529
  requestArgumentsSerialized: querySerialized,
505
530
  request: axiosActiveRequest,
531
+ allowListStatus: cacheAllowListStatus,
506
532
  response,
507
533
  });
508
534
  }
@@ -582,6 +608,7 @@ export class HttpExecutor<
582
608
  setCache<ResponseDataType>(requestMethod, tableName, cacheRequestData, {
583
609
  requestArgumentsSerialized: querySerialized,
584
610
  request: axiosActiveRequest,
611
+ allowListStatus: cacheAllowListStatus,
585
612
  response,
586
613
  final: !hasNext,
587
614
  });
@@ -870,6 +897,7 @@ export class HttpExecutor<
870
897
  setCache<ResponseDataType>(requestMethod, tableName, cacheRequestData, {
871
898
  requestArgumentsSerialized: querySerialized,
872
899
  request: axiosActiveRequest,
900
+ allowListStatus: cacheAllowListStatus,
873
901
  response,
874
902
  final: true,
875
903
  });