@carbonorm/carbonnode 6.0.14 → 6.0.18

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 (77) hide show
  1. package/dist/executors/SqlExecutor.d.ts +17 -0
  2. package/dist/index.cjs.js +450 -248
  3. package/dist/index.cjs.js.map +1 -1
  4. package/dist/index.esm.js +450 -249
  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 -1
  8. package/dist/utils/logLevel.d.ts +3 -3
  9. package/dist/utils/logSql.d.ts +10 -1
  10. package/package.json +2 -2
  11. package/scripts/assets/handlebars/C6.ts.handlebars +1 -1
  12. package/src/__tests__/fixtures/sqlResponses/sqlAllowList.json +1 -1
  13. package/src/__tests__/httpExecutor.cacheEviction.test.ts +70 -0
  14. package/src/__tests__/logSql.test.ts +54 -2
  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 +59 -70
  19. package/src/__tests__/sakila-db/C6.ts +2 -2
  20. package/src/__tests__/sakila-db/sqlResponses/C6.actor.post.json +3 -3
  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 +5 -5
  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 +2 -2
  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 +2 -2
  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 +2 -2
  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 +5 -5
  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 +2 -2
  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 +1 -1
  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 +2 -2
  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 +2 -2
  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.join.json +10 -10
  60. package/src/__tests__/sakila-db/sqlResponses/C6.rental.post.json +3 -3
  61. package/src/__tests__/sakila-db/sqlResponses/C6.rental.post.latest.json +3 -3
  62. package/src/__tests__/sakila-db/sqlResponses/C6.rental.put.json +1 -1
  63. package/src/__tests__/sakila-db/sqlResponses/C6.rental.put.lookup.json +3 -3
  64. package/src/__tests__/sqlAllowList.test.ts +100 -0
  65. package/src/__tests__/sqlBuilders.test.ts +3 -4
  66. package/src/__tests__/sqlExecutor.cacheEviction.test.ts +79 -0
  67. package/src/executors/HttpExecutor.ts +20 -4
  68. package/src/executors/SqlExecutor.ts +131 -12
  69. package/src/orm/queries/DeleteQueryBuilder.ts +0 -4
  70. package/src/orm/queries/PostQueryBuilder.ts +0 -4
  71. package/src/orm/queries/SelectQueryBuilder.ts +0 -4
  72. package/src/orm/queries/UpdateQueryBuilder.ts +0 -4
  73. package/src/types/ormInterfaces.ts +4 -1
  74. package/src/utils/cacheManager.ts +26 -9
  75. package/src/utils/logLevel.ts +3 -4
  76. package/src/utils/logSql.ts +51 -6
  77. package/src/utils/sqlAllowList.ts +111 -9
@@ -5,8 +5,8 @@
5
5
  "sql": {
6
6
  "sql": "INSERT INTO `language` (\n `name`, `last_update`\n ) VALUES\n (?, ?)",
7
7
  "values": [
8
- "name_1770597283831",
9
- "2026-02-09 00:34:43"
8
+ "name_1770934052102",
9
+ "2026-02-12 22:07:32"
10
10
  ]
11
11
  }
12
12
  }
@@ -2,8 +2,8 @@
2
2
  "rest": [
3
3
  {
4
4
  "language_id": 7,
5
- "name": "name_1770597283831",
6
- "last_update": "2026-02-09T00:34:43.000Z"
5
+ "name": "name_1770934052102",
6
+ "last_update": "2026-02-12T22:07:32.000Z"
7
7
  }
8
8
  ],
9
9
  "sql": {
@@ -5,7 +5,7 @@
5
5
  "sql": {
6
6
  "sql": "UPDATE `language` SET `name` = ? WHERE (language.language_id) = ?",
7
7
  "values": [
8
- "name_updated_1770597",
8
+ "name_updated_1770934",
9
9
  7
10
10
  ]
11
11
  }
@@ -2,8 +2,8 @@
2
2
  "rest": [
3
3
  {
4
4
  "language_id": 7,
5
- "name": "name_updated_1770597",
6
- "last_update": "2026-02-09T00:34:43.000Z"
5
+ "name": "name_updated_1770934",
6
+ "last_update": "2026-02-12T22:07:32.000Z"
7
7
  }
8
8
  ],
9
9
  "sql": {
@@ -9,8 +9,8 @@
9
9
  1,
10
10
  1,
11
11
  1,
12
- "2026-02-09 00:34:43",
13
- "2026-02-09 00:34:43"
12
+ "2026-02-12 22:07:32",
13
+ "2026-02-12 22:07:32"
14
14
  ]
15
15
  }
16
16
  }
@@ -6,8 +6,8 @@
6
6
  "staff_id": 1,
7
7
  "rental_id": 1,
8
8
  "amount": "1.00",
9
- "payment_date": "2026-02-09T00:34:43.000Z",
10
- "last_update": "2026-02-09T00:34:43.000Z"
9
+ "payment_date": "2026-02-12T22:07:32.000Z",
10
+ "last_update": "2026-02-12T22:07:32.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-09T00:34:43.000Z",
10
- "last_update": "2026-02-09T00:34:43.000Z"
9
+ "payment_date": "2026-02-12T22:07:32.000Z",
10
+ "last_update": "2026-02-12T22:07:32.000Z"
11
11
  }
12
12
  ],
13
13
  "sql": {
@@ -1,18 +1,18 @@
1
1
  {
2
2
  "rest": [
3
3
  {
4
- "rental_id": 76,
5
- "rental_date": "2005-05-25T11:30:37.000Z",
6
- "inventory_id": 3021,
7
- "customer_id": 1,
8
- "return_date": "2005-06-03T12:00:37.000Z",
9
- "staff_id": 2,
4
+ "rental_id": 1,
5
+ "rental_date": "2005-05-24T22:53:30.000Z",
6
+ "inventory_id": 367,
7
+ "customer_id": 130,
8
+ "return_date": "2005-05-26T22:04:30.000Z",
9
+ "staff_id": 1,
10
10
  "last_update": "2006-02-15T04:57:20.000Z",
11
11
  "store_id": 1,
12
- "first_name": "MARY",
13
- "last_name": "SMITH",
14
- "email": "MARY.SMITH@sakilacustomer.org",
15
- "address_id": 5,
12
+ "first_name": "CHARLOTTE",
13
+ "last_name": "HUNTER",
14
+ "email": "CHARLOTTE.HUNTER@sakilacustomer.org",
15
+ "address_id": 134,
16
16
  "active": 1,
17
17
  "create_date": "2006-02-14T22:04:36.000Z"
18
18
  }
@@ -5,12 +5,12 @@
5
5
  "sql": {
6
6
  "sql": "INSERT INTO `rental` (\n `rental_date`, `inventory_id`, `customer_id`, `return_date`, `staff_id`, `last_update`\n ) VALUES\n (?, ?, ?, ?, ?, ?)",
7
7
  "values": [
8
- "2026-02-09 00:34:43",
8
+ "2026-02-12 22:07:32",
9
9
  1,
10
10
  1,
11
- "2026-02-09 00:34:43",
11
+ "2026-02-12 22:07:32",
12
12
  1,
13
- "2026-02-09 00:34:43"
13
+ "2026-02-12 22:07:32"
14
14
  ]
15
15
  }
16
16
  }
@@ -2,12 +2,12 @@
2
2
  "rest": [
3
3
  {
4
4
  "rental_id": 16050,
5
- "rental_date": "2026-02-09T00:34:43.000Z",
5
+ "rental_date": "2026-02-12T22:07:32.000Z",
6
6
  "inventory_id": 1,
7
7
  "customer_id": 1,
8
- "return_date": "2026-02-09T00:34:43.000Z",
8
+ "return_date": "2026-02-12T22:07:32.000Z",
9
9
  "staff_id": 1,
10
- "last_update": "2026-02-09T00:34:43.000Z"
10
+ "last_update": "2026-02-12T22:07:32.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-09 00:34:43",
8
+ "2026-02-12 22:07:32",
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-09T00:34:43.000Z",
5
+ "rental_date": "2026-02-12T22:07:32.000Z",
6
6
  "inventory_id": 1,
7
7
  "customer_id": 1,
8
- "return_date": "2026-02-09T00:34:43.000Z",
8
+ "return_date": "2026-02-12T22:07:32.000Z",
9
9
  "staff_id": 1,
10
- "last_update": "2026-02-09T00:34:43.000Z"
10
+ "last_update": "2026-02-12T22:07:32.000Z"
11
11
  }
12
12
  ],
13
13
  "sql": {
@@ -119,10 +119,110 @@ describe("SQL allowlist", () => {
119
119
  await expect(
120
120
  Actor.Get({
121
121
  [C6.PAGINATION]: {[C6.LIMIT]: 1},
122
+ cacheResults: false,
122
123
  } as any)
123
124
  ).rejects.toThrow("SQL statement is not permitted");
124
125
  } finally {
125
126
  restoreGlobals(originalGlobals);
126
127
  }
127
128
  });
129
+
130
+ it("normalizes multi-row VALUES with variable row counts", () => {
131
+ const oneRow = `
132
+ INSERT INTO \`valuation_report_comparables\` (\`report_id\`, \`unit_id\`, \`subject_unit_id\`)
133
+ VALUES (?, ?, ?)
134
+ ON DUPLICATE KEY UPDATE \`subject_unit_id\` = VALUES(\`subject_unit_id\`)
135
+ `;
136
+ const manyRows = `
137
+ INSERT INTO \`valuation_report_comparables\` (\`report_id\`, \`unit_id\`, \`subject_unit_id\`)
138
+ VALUES (?, ?, ?), (?, ?, ?), (?, ?, ?), (?, ?, ?)
139
+ ON DUPLICATE KEY UPDATE \`subject_unit_id\` = VALUES(\`subject_unit_id\`)
140
+ `;
141
+
142
+ expect(normalizeSql(oneRow)).toContain("VALUES (? ×3) ×*");
143
+ expect(normalizeSql(manyRows)).toContain("VALUES (? ×3) ×*");
144
+ expect(normalizeSql(oneRow)).toBe(normalizeSql(manyRows));
145
+ });
146
+
147
+ it("normalizes IN bind list cardinality", () => {
148
+ const smallIn = "SELECT * FROM `geometries` WHERE ( geometries.geometry_id IN (?, ?, ?) ) LIMIT 100";
149
+ const largeIn = "SELECT * FROM `geometries` WHERE ( geometries.geometry_id IN (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ) LIMIT 250";
150
+
151
+ const normalizedSmall = normalizeSql(smallIn);
152
+ const normalizedLarge = normalizeSql(largeIn);
153
+
154
+ expect(normalizedSmall).toContain("IN (? ×*)");
155
+ expect(normalizedLarge).toContain("IN (? ×*)");
156
+ expect(normalizedSmall).toContain("LIMIT ?");
157
+ expect(normalizedLarge).toContain("LIMIT ?");
158
+ expect(normalizedSmall).toBe(normalizedLarge);
159
+ });
160
+
161
+ it("normalizes LIMIT and OFFSET numeric literals", () => {
162
+ expect(normalizeSql("SELECT * FROM `actor` LIMIT 100")).toBe(
163
+ normalizeSql("SELECT * FROM `actor` LIMIT 25"),
164
+ );
165
+ expect(normalizeSql("SELECT * FROM `actor` LIMIT 10, 50")).toBe(
166
+ "SELECT * FROM `actor` LIMIT ?, ?",
167
+ );
168
+ expect(normalizeSql("SELECT * FROM `actor` LIMIT 50 OFFSET 100")).toBe(
169
+ "SELECT * FROM `actor` LIMIT ? OFFSET ?",
170
+ );
171
+ });
172
+
173
+ it("normalizes variable ST_GEOMFROMTEXT POINT and POLYGON literals", () => {
174
+ const q1 = `
175
+ SELECT * FROM \`property_units\`
176
+ WHERE MBRCONTAINS(
177
+ ST_GEOMFROMTEXT('POLYGON((39.1 -105.1, 39.2 -105.1, 39.2 -105.0, 39.1 -105.0, 39.1 -105.1))', 4326),
178
+ property_units.location
179
+ )
180
+ ORDER BY ST_DISTANCE_SPHERE(
181
+ property_units.location,
182
+ ST_GEOMFROMTEXT('POINT(39.15 -105.05)', 4326)
183
+ )
184
+ LIMIT 100
185
+ `;
186
+ const q2 = `
187
+ SELECT * FROM \`property_units\`
188
+ WHERE MBRCONTAINS(
189
+ ST_GEOMFROMTEXT('POLYGON((39.3 -105.3, 39.7 -105.3, 39.7 -104.8, 39.3 -104.8, 39.3 -105.3))', 4326),
190
+ property_units.location
191
+ )
192
+ ORDER BY ST_DISTANCE_SPHERE(
193
+ property_units.location,
194
+ ST_GEOMFROMTEXT('POINT(39.5321821 -105.0035613)', 4326)
195
+ )
196
+ LIMIT 250
197
+ `;
198
+
199
+ const normalized1 = normalizeSql(q1);
200
+ const normalized2 = normalizeSql(q2);
201
+
202
+ expect(normalized1).toContain("ST_GEOMFROMTEXT('POLYGON((?))', ?)");
203
+ expect(normalized1).toContain("ST_GEOMFROMTEXT('POINT(? ?)', ?)");
204
+ expect(normalized1).toContain("ST_DISTANCE_SPHERE(");
205
+ expect(normalized1).toContain("LIMIT ?");
206
+ expect(normalized1).toBe(normalized2);
207
+ });
208
+
209
+ it("normalizes geo function casing and FORCE INDEX spacing", () => {
210
+ const a =
211
+ "SELECT * FROM `property_units` FORCE INDEX (`idx_county_id`,`idx_property_units_location`) WHERE ST_Distance_Sphere(property_units.location, ST_GeomFromText('POINT(39.5 -105.0)', 4326)) <= 50 LIMIT 100";
212
+ const b =
213
+ "SELECT * FROM `property_units` FORCE INDEX (`idx_county_id`, `idx_property_units_location`) WHERE st_distance_sphere(property_units.location, st_geomfromtext('POINT(39.7 -104.9)', 4326)) <= 25 LIMIT 25";
214
+
215
+ const normalizedA = normalizeSql(a);
216
+ const normalizedB = normalizeSql(b);
217
+
218
+ expect(normalizedA).toContain("FORCE INDEX (`idx_county_id`, `idx_property_units_location`)");
219
+ expect(normalizedA).toContain("ST_DISTANCE_SPHERE");
220
+ expect(normalizedA).toContain("ST_GEOMFROMTEXT('POINT(? ?)', ?)");
221
+ expect(normalizedA).toContain("LIMIT ?");
222
+ expect(normalizedB).toContain("FORCE INDEX (`idx_county_id`, `idx_property_units_location`)");
223
+ expect(normalizedB).toContain("ST_DISTANCE_SPHERE");
224
+ expect(normalizedB).toContain("ST_GEOMFROMTEXT('POINT(? ?)', ?)");
225
+ expect(normalizedB).toContain("LIMIT ?");
226
+ });
227
+
128
228
  });
@@ -5,7 +5,6 @@ import { PostQueryBuilder } from '../orm/queries/PostQueryBuilder';
5
5
  import { UpdateQueryBuilder } from '../orm/queries/UpdateQueryBuilder';
6
6
  import { DeleteQueryBuilder } from '../orm/queries/DeleteQueryBuilder';
7
7
  import { buildTestConfig, buildBinaryTestConfig, buildBinaryTestConfigFqn } from './fixtures/c6.fixture';
8
- import { version } from '../../package.json';
9
8
 
10
9
  describe('SQL Builders', () => {
11
10
  it('builds SELECT with JOIN, WHERE, GROUP BY, HAVING and default LIMIT', () => {
@@ -44,11 +43,11 @@ describe('SQL Builders', () => {
44
43
  expect(params).toEqual(['%A%', 10, 1]);
45
44
  });
46
45
 
47
- it('logs SELECT statements with package version', () => {
46
+ it('logs SELECT aggregate expressions at DEBUG level', () => {
48
47
  const config = buildTestConfig();
49
48
  const logSpy = vi.spyOn(console, 'log').mockImplementation(() => undefined);
50
49
  const qb = new SelectQueryBuilder(config as any, {
51
- SELECT: ['actor.first_name'],
50
+ SELECT: [[C6C.COUNT, 'actor.actor_id', C6C.AS, 'cnt']],
52
51
  } as any, false);
53
52
 
54
53
  qb.build('actor');
@@ -59,7 +58,7 @@ describe('SQL Builders', () => {
59
58
  const selectLine = logLines.find((line) => line.includes('[SELECT]'));
60
59
 
61
60
  expect(selectLine).toBeDefined();
62
- expect(selectLine).toContain(`[${version}]`);
61
+ expect(selectLine).toContain('COUNT(actor.actor_id) AS cnt');
63
62
  logSpy.mockRestore();
64
63
  });
65
64
 
@@ -0,0 +1,79 @@
1
+ import { beforeEach, describe, expect, it, vi } from "vitest";
2
+ import { restOrm } from "../api/restOrm";
3
+ import { apiRequestCache, clearCache } from "../utils/cacheManager";
4
+ import { buildTestConfig } from "./fixtures/c6.fixture";
5
+
6
+ describe("SqlExecutor cache eviction", () => {
7
+ const rows = [
8
+ {
9
+ actor_id: 1,
10
+ first_name: "ALICE",
11
+ last_name: "ONE",
12
+ },
13
+ ];
14
+
15
+ const buildOrm = () => {
16
+ const conn: any = {
17
+ beginTransaction: vi.fn(async () => undefined),
18
+ query: vi.fn(async () => [rows, []]),
19
+ commit: vi.fn(async () => undefined),
20
+ rollback: vi.fn(async () => undefined),
21
+ release: vi.fn(),
22
+ };
23
+
24
+ const baseConfig = buildTestConfig() as any;
25
+
26
+ const actorSql = restOrm<any>(() => ({
27
+ ...baseConfig,
28
+ mysqlPool: {
29
+ getConnection: vi.fn(async () => conn),
30
+ },
31
+ verbose: false,
32
+ }));
33
+
34
+ return {
35
+ actorSql,
36
+ conn,
37
+ };
38
+ };
39
+
40
+ beforeEach(() => {
41
+ clearCache({ ignoreWarning: true });
42
+ });
43
+
44
+ it("adds evictFromCache for cached GET responses", async () => {
45
+ const { actorSql, conn } = buildOrm();
46
+
47
+ const response = await actorSql.Get({ actor_id: 1, cacheResults: true } as any);
48
+
49
+ expect(conn.query).toHaveBeenCalledTimes(1);
50
+ expect(typeof response.evictFromCache).toBe("function");
51
+ expect(apiRequestCache.size).toBe(1);
52
+
53
+ expect(response.evictFromCache?.()).toBe(true);
54
+ expect(apiRequestCache.size).toBe(0);
55
+ expect(response.evictFromCache?.()).toBe(false);
56
+ });
57
+
58
+ it("does not add evictFromCache when cacheResults is false", async () => {
59
+ const { actorSql, conn } = buildOrm();
60
+
61
+ const response = await actorSql.Get({ actor_id: 1, cacheResults: false } as any);
62
+
63
+ expect(conn.query).toHaveBeenCalledTimes(1);
64
+ expect(response.evictFromCache).toBeUndefined();
65
+ expect(apiRequestCache.size).toBe(0);
66
+ });
67
+
68
+ it("keeps evictFromCache on cache hits", async () => {
69
+ const { actorSql, conn } = buildOrm();
70
+
71
+ await actorSql.Get({ actor_id: 1, cacheResults: true } as any);
72
+ const cached = await actorSql.Get({ actor_id: 1, cacheResults: true } as any);
73
+
74
+ expect(conn.query).toHaveBeenCalledTimes(1);
75
+ expect(typeof cached.evictFromCache).toBe("function");
76
+ expect(cached.evictFromCache?.()).toBe(true);
77
+ expect(apiRequestCache.size).toBe(0);
78
+ });
79
+ });
@@ -13,7 +13,7 @@ import {
13
13
  PUT, RequestQueryBody
14
14
  } from "../types/ormInterfaces";
15
15
  import {removeInvalidKeys, removePrefixIfExists, TestRestfulResponse} from "../utils/apiHelpers";
16
- import {checkCache, setCache, userCustomClearCache} from "../utils/cacheManager";
16
+ import {checkCache, evictCacheEntry, setCache, userCustomClearCache} from "../utils/cacheManager";
17
17
  import {sortAndSerializeQueryObject} from "../utils/sortAndSerializeQueryObject";
18
18
  import {notifyToast} from "../utils/toastRuntime";
19
19
  import {Executor} from "./Executor";
@@ -254,17 +254,29 @@ export class HttpExecutor<
254
254
  G['RequestTableOverrides']
255
255
  >;
256
256
 
257
+ const evictFromCache =
258
+ requestMethod === GET && cacheResults
259
+ ? () => evictCacheEntry(requestMethod, tableName, cacheRequestData)
260
+ : undefined;
261
+
257
262
  // literally impossible for query to be undefined or null here but the editor is too busy licking windows to understand that
258
263
  let querySerialized: string = sortAndSerializeQueryObject(tables, cacheRequestData ?? {});
259
264
 
260
265
  let cachedRequest: Promise<{ data: ResponseDataType }> | false = false;
261
266
 
262
267
  if (cacheResults) {
263
- cachedRequest = checkCache<ResponseDataType>(requestMethod, tableName, cacheRequestData);
268
+ cachedRequest = checkCache<ResponseDataType>(requestMethod, tableName, cacheRequestData, logContext);
264
269
  }
265
270
 
266
271
  if (cachedRequest) {
267
- return (await cachedRequest).data;
272
+ const cachedData = (await cachedRequest).data;
273
+ if (evictFromCache
274
+ && cachedData
275
+ && typeof cachedData === "object"
276
+ && Array.isArray((cachedData as C6RestResponse<'GET', G['RestTableInterface']>).rest)) {
277
+ (cachedData as C6RestResponse<'GET', G['RestTableInterface']>).evictFromCache = evictFromCache;
278
+ }
279
+ return cachedData;
268
280
  }
269
281
 
270
282
  if (cacheResults) {
@@ -544,7 +556,7 @@ export class HttpExecutor<
544
556
  callback();
545
557
  }
546
558
 
547
- if (C6.GET === requestMethod && this.isRestResponse(response)) {
559
+ if (requestMethod === GET && this.isRestResponse(response)) {
548
560
 
549
561
  const responseData =
550
562
  response.data as DetermineResponseDataType<'GET', G['RestTableInterface']>;
@@ -562,6 +574,10 @@ export class HttpExecutor<
562
574
  responseData.next = undefined; // short page => done
563
575
  }
564
576
 
577
+ if (cachingConfirmed && evictFromCache) {
578
+ responseData.evictFromCache = evictFromCache;
579
+ }
580
+
565
581
  if (cachingConfirmed) {
566
582
  setCache<ResponseDataType>(requestMethod, tableName, cacheRequestData, {
567
583
  requestArgumentsSerialized: querySerialized,