@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.
- package/dist/executors/SqlExecutor.d.ts +7 -0
- package/dist/index.cjs.js +230 -20
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.esm.js +230 -20
- package/dist/index.esm.js.map +1 -1
- package/dist/types/ormInterfaces.d.ts +1 -0
- package/dist/utils/cacheManager.d.ts +3 -2
- package/dist/utils/logSql.d.ts +1 -1
- package/package.json +1 -1
- package/src/__tests__/cacheManager.test.ts +55 -1
- package/src/__tests__/logSql.test.ts +16 -0
- package/src/__tests__/sakila-db/C6.js +1 -1
- package/src/__tests__/sakila-db/C6.mysqldump.json +1 -1
- package/src/__tests__/sakila-db/C6.mysqldump.sql +1 -1
- package/src/__tests__/sakila-db/C6.ts +1 -1
- package/src/__tests__/sakila-db/sqlResponses/C6.actor.post.json +11 -4
- package/src/__tests__/sakila-db/sqlResponses/C6.actor.post.latest.json +3 -3
- package/src/__tests__/sakila-db/sqlResponses/C6.actor.put.json +1 -1
- package/src/__tests__/sakila-db/sqlResponses/C6.actor.put.lookup.json +3 -3
- package/src/__tests__/sakila-db/sqlResponses/C6.address.post.json +18 -6
- package/src/__tests__/sakila-db/sqlResponses/C6.address.post.latest.json +5 -5
- package/src/__tests__/sakila-db/sqlResponses/C6.address.put.json +1 -1
- package/src/__tests__/sakila-db/sqlResponses/C6.address.put.lookup.json +5 -5
- package/src/__tests__/sakila-db/sqlResponses/C6.category.post.json +9 -3
- package/src/__tests__/sakila-db/sqlResponses/C6.category.post.latest.json +2 -2
- package/src/__tests__/sakila-db/sqlResponses/C6.category.put.json +1 -1
- package/src/__tests__/sakila-db/sqlResponses/C6.category.put.lookup.json +2 -2
- package/src/__tests__/sakila-db/sqlResponses/C6.city.post.json +10 -3
- package/src/__tests__/sakila-db/sqlResponses/C6.city.post.latest.json +2 -2
- package/src/__tests__/sakila-db/sqlResponses/C6.city.put.json +1 -1
- package/src/__tests__/sakila-db/sqlResponses/C6.city.put.lookup.json +2 -2
- package/src/__tests__/sakila-db/sqlResponses/C6.country.post.json +9 -3
- package/src/__tests__/sakila-db/sqlResponses/C6.country.post.latest.json +2 -2
- package/src/__tests__/sakila-db/sqlResponses/C6.country.put.json +1 -1
- package/src/__tests__/sakila-db/sqlResponses/C6.country.put.lookup.json +2 -2
- package/src/__tests__/sakila-db/sqlResponses/C6.customer.post.json +18 -6
- package/src/__tests__/sakila-db/sqlResponses/C6.customer.post.latest.json +5 -5
- package/src/__tests__/sakila-db/sqlResponses/C6.customer.put.json +1 -1
- package/src/__tests__/sakila-db/sqlResponses/C6.customer.put.lookup.json +5 -5
- package/src/__tests__/sakila-db/sqlResponses/C6.film.post.json +18 -3
- package/src/__tests__/sakila-db/sqlResponses/C6.film.post.latest.json +2 -2
- package/src/__tests__/sakila-db/sqlResponses/C6.film.put.json +1 -1
- package/src/__tests__/sakila-db/sqlResponses/C6.film.put.lookup.json +2 -2
- package/src/__tests__/sakila-db/sqlResponses/C6.inventory.post.json +9 -2
- package/src/__tests__/sakila-db/sqlResponses/C6.inventory.post.latest.json +1 -1
- package/src/__tests__/sakila-db/sqlResponses/C6.inventory.put.json +1 -1
- package/src/__tests__/sakila-db/sqlResponses/C6.inventory.put.lookup.json +1 -1
- package/src/__tests__/sakila-db/sqlResponses/C6.language.post.json +9 -3
- package/src/__tests__/sakila-db/sqlResponses/C6.language.post.latest.json +2 -2
- package/src/__tests__/sakila-db/sqlResponses/C6.language.put.json +1 -1
- package/src/__tests__/sakila-db/sqlResponses/C6.language.put.lookup.json +2 -2
- package/src/__tests__/sakila-db/sqlResponses/C6.payment.post.json +13 -3
- package/src/__tests__/sakila-db/sqlResponses/C6.payment.post.latest.json +2 -2
- package/src/__tests__/sakila-db/sqlResponses/C6.payment.put.lookup.json +2 -2
- package/src/__tests__/sakila-db/sqlResponses/C6.rental.post.json +14 -4
- package/src/__tests__/sakila-db/sqlResponses/C6.rental.post.latest.json +3 -3
- package/src/__tests__/sakila-db/sqlResponses/C6.rental.put.json +1 -1
- package/src/__tests__/sakila-db/sqlResponses/C6.rental.put.lookup.json +3 -3
- package/src/__tests__/sqlExecutorPostUuid.test.ts +185 -0
- package/src/executors/HttpExecutor.ts +33 -5
- package/src/executors/SqlExecutor.ts +207 -3
- package/src/types/ormInterfaces.ts +1 -0
- package/src/utils/cacheManager.ts +22 -5
- package/src/utils/logSql.ts +3 -1
|
@@ -27,6 +27,43 @@ import { getLogContext, LogLevel, logWithLevel } from "../utils/logLevel";
|
|
|
27
27
|
|
|
28
28
|
const SQL_ALLOWLIST_BLOCKED_CODE = "SQL_ALLOWLIST_BLOCKED";
|
|
29
29
|
|
|
30
|
+
const fillRandomBytes = (bytes: Uint8Array): void => {
|
|
31
|
+
const cryptoRef = (globalThis as { crypto?: Crypto }).crypto;
|
|
32
|
+
if (!cryptoRef || typeof cryptoRef.getRandomValues !== "function") {
|
|
33
|
+
throw new Error("Secure random source unavailable: crypto.getRandomValues is required for UUID generation.");
|
|
34
|
+
}
|
|
35
|
+
cryptoRef.getRandomValues(bytes);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const generateUuidV7 = (): string => {
|
|
39
|
+
const bytes = new Uint8Array(16);
|
|
40
|
+
const random = new Uint8Array(10);
|
|
41
|
+
fillRandomBytes(random);
|
|
42
|
+
|
|
43
|
+
const timestampMs = Date.now();
|
|
44
|
+
bytes[0] = Math.floor(timestampMs / 1099511627776) & 0xff; // 2^40
|
|
45
|
+
bytes[1] = Math.floor(timestampMs / 4294967296) & 0xff; // 2^32
|
|
46
|
+
bytes[2] = Math.floor(timestampMs / 16777216) & 0xff; // 2^24
|
|
47
|
+
bytes[3] = Math.floor(timestampMs / 65536) & 0xff; // 2^16
|
|
48
|
+
bytes[4] = Math.floor(timestampMs / 256) & 0xff; // 2^8
|
|
49
|
+
bytes[5] = timestampMs & 0xff;
|
|
50
|
+
|
|
51
|
+
// RFC 9562 UUIDv7 layout
|
|
52
|
+
bytes[6] = 0x70 | (random[0] & 0x0f); // version 7 + rand_a high bits
|
|
53
|
+
bytes[7] = random[1]; // rand_a low bits
|
|
54
|
+
bytes[8] = 0x80 | (random[2] & 0x3f); // variant + rand_b high bits
|
|
55
|
+
bytes[9] = random[3];
|
|
56
|
+
bytes[10] = random[4];
|
|
57
|
+
bytes[11] = random[5];
|
|
58
|
+
bytes[12] = random[6];
|
|
59
|
+
bytes[13] = random[7];
|
|
60
|
+
bytes[14] = random[8];
|
|
61
|
+
bytes[15] = random[9];
|
|
62
|
+
|
|
63
|
+
const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
64
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
65
|
+
};
|
|
66
|
+
|
|
30
67
|
export type SqlAllowListBlockedError = Error & {
|
|
31
68
|
code: typeof SQL_ALLOWLIST_BLOCKED_CODE;
|
|
32
69
|
tableName?: string;
|
|
@@ -72,6 +109,157 @@ const createSqlAllowListBlockedError = (args: {
|
|
|
72
109
|
export class SqlExecutor<
|
|
73
110
|
G extends OrmGenerics
|
|
74
111
|
> extends Executor<G> {
|
|
112
|
+
private getPostRequestRows(): Record<string, any>[] {
|
|
113
|
+
const request = this.request as any;
|
|
114
|
+
if (!request) return [];
|
|
115
|
+
|
|
116
|
+
if (Array.isArray(request)) {
|
|
117
|
+
return request as Record<string, any>[];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (
|
|
121
|
+
Array.isArray(request.dataInsertMultipleRows)
|
|
122
|
+
&& request.dataInsertMultipleRows.length > 0
|
|
123
|
+
) {
|
|
124
|
+
return request.dataInsertMultipleRows as Record<string, any>[];
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const verb = C6C.REPLACE in request ? C6C.REPLACE : C6C.INSERT;
|
|
128
|
+
if (verb in request && request[verb] && typeof request[verb] === "object") {
|
|
129
|
+
return [request[verb] as Record<string, any>];
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (typeof request === "object") {
|
|
133
|
+
return [request as Record<string, any>];
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return [];
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
private getTypeValidationForColumn(shortKey: string, fullKey: string): Record<string, any> | undefined {
|
|
140
|
+
const validation = (this.config.restModel as any)?.TYPE_VALIDATION;
|
|
141
|
+
if (!validation || typeof validation !== "object") return undefined;
|
|
142
|
+
return validation[shortKey] ?? validation[fullKey];
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
private isUuidLikePrimaryColumn(columnDef: Record<string, any> | undefined): boolean {
|
|
146
|
+
if (!columnDef || typeof columnDef !== "object") return false;
|
|
147
|
+
if (columnDef.AUTO_INCREMENT === true) return false;
|
|
148
|
+
|
|
149
|
+
const mysqlType = String(columnDef.MYSQL_TYPE ?? "").toLowerCase();
|
|
150
|
+
const maxLength = String(columnDef.MAX_LENGTH ?? "").trim();
|
|
151
|
+
|
|
152
|
+
if (mysqlType.includes("uuid")) return true;
|
|
153
|
+
|
|
154
|
+
const isBinary16 = mysqlType.includes("binary")
|
|
155
|
+
&& (maxLength === "16" || /\b16\b/.test(mysqlType) || mysqlType === "binary");
|
|
156
|
+
const isUuidString = (mysqlType.includes("char") || mysqlType.includes("varchar"))
|
|
157
|
+
&& (maxLength === "32" || maxLength === "36");
|
|
158
|
+
|
|
159
|
+
return isBinary16 || isUuidString;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
private hasDefinedValue(value: unknown): boolean {
|
|
163
|
+
if (value === undefined || value === null) return false;
|
|
164
|
+
if (typeof value === "string" && value.trim() === "") return false;
|
|
165
|
+
return true;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
private generatePrimaryUuidValue(columnDef: Record<string, any>): string {
|
|
169
|
+
const mysqlType = String(columnDef.MYSQL_TYPE ?? "").toLowerCase();
|
|
170
|
+
const maxLength = String(columnDef.MAX_LENGTH ?? "").trim();
|
|
171
|
+
const uuid = generateUuidV7();
|
|
172
|
+
|
|
173
|
+
// BINARY(16) and CHAR/VARCHAR(32) commonly persist UUIDs as 32-hex.
|
|
174
|
+
if (mysqlType.includes("binary") || maxLength === "32") {
|
|
175
|
+
return uuid.replace(/-/g, "").toUpperCase();
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return uuid;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
private assignMissingPostPrimaryUuids(): void {
|
|
182
|
+
if (this.config.requestMethod !== C6C.POST) return;
|
|
183
|
+
|
|
184
|
+
const rows = this.getPostRequestRows();
|
|
185
|
+
if (rows.length === 0) return;
|
|
186
|
+
|
|
187
|
+
const columns = this.config.restModel.COLUMNS as Record<string, string>;
|
|
188
|
+
const tableName = this.config.restModel.TABLE_NAME as string;
|
|
189
|
+
const primaryShorts = this.config.restModel.PRIMARY_SHORT ?? [];
|
|
190
|
+
|
|
191
|
+
const primaryColumns = primaryShorts
|
|
192
|
+
.map((shortKey) => {
|
|
193
|
+
const fullKey = Object.keys(columns).find((key) => columns[key] === shortKey)
|
|
194
|
+
?? `${tableName}.${shortKey}`;
|
|
195
|
+
const columnDef = this.getTypeValidationForColumn(shortKey, fullKey);
|
|
196
|
+
return { shortKey, fullKey, columnDef };
|
|
197
|
+
})
|
|
198
|
+
.filter(({ columnDef }) => this.isUuidLikePrimaryColumn(columnDef));
|
|
199
|
+
|
|
200
|
+
if (primaryColumns.length === 0) return;
|
|
201
|
+
|
|
202
|
+
for (const row of rows) {
|
|
203
|
+
if (!row || typeof row !== "object") continue;
|
|
204
|
+
|
|
205
|
+
const useQualifiedKeyByDefault = Object.keys(row).some((key) => key.includes("."));
|
|
206
|
+
|
|
207
|
+
for (const primaryColumn of primaryColumns) {
|
|
208
|
+
const existing = row[primaryColumn.shortKey] ?? row[primaryColumn.fullKey];
|
|
209
|
+
if (this.hasDefinedValue(existing)) continue;
|
|
210
|
+
|
|
211
|
+
const generated = this.generatePrimaryUuidValue(primaryColumn.columnDef!);
|
|
212
|
+
if (Object.prototype.hasOwnProperty.call(row, primaryColumn.shortKey)) {
|
|
213
|
+
row[primaryColumn.shortKey] = generated;
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
if (Object.prototype.hasOwnProperty.call(row, primaryColumn.fullKey)) {
|
|
217
|
+
row[primaryColumn.fullKey] = generated;
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
row[useQualifiedKeyByDefault ? primaryColumn.fullKey : primaryColumn.shortKey] = generated;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
private buildPostResponseRows(insertId?: number | string): Record<string, unknown>[] {
|
|
227
|
+
const rows = this.getPostRequestRows();
|
|
228
|
+
if (rows.length === 0) return [];
|
|
229
|
+
|
|
230
|
+
const columns = this.config.restModel.COLUMNS as Record<string, string>;
|
|
231
|
+
const validColumns = new Set(Object.values(columns));
|
|
232
|
+
const pkShorts = this.config.restModel.PRIMARY_SHORT ?? [];
|
|
233
|
+
const now = new Date().toISOString();
|
|
234
|
+
|
|
235
|
+
return rows.map((row, index) => {
|
|
236
|
+
const normalized = this.normalizeRequestPayload(row ?? {});
|
|
237
|
+
|
|
238
|
+
if (validColumns.has("changed_at") && normalized.changed_at === undefined) {
|
|
239
|
+
normalized.changed_at = now;
|
|
240
|
+
}
|
|
241
|
+
if (validColumns.has("created_at") && normalized.created_at === undefined) {
|
|
242
|
+
normalized.created_at = now;
|
|
243
|
+
}
|
|
244
|
+
if (validColumns.has("updated_at") && normalized.updated_at === undefined) {
|
|
245
|
+
normalized.updated_at = now;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// When DB generated PK is numeric/autoincrement, expose it for the single-row insert.
|
|
249
|
+
if (
|
|
250
|
+
index === 0
|
|
251
|
+
&& insertId !== undefined
|
|
252
|
+
&& insertId !== null
|
|
253
|
+
&& pkShorts.length === 1
|
|
254
|
+
&& !this.hasDefinedValue(normalized[pkShorts[0]])
|
|
255
|
+
) {
|
|
256
|
+
normalized[pkShorts[0]] = insertId;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return normalized;
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
|
|
75
263
|
private resolveSqlLogMethod(method: iRestMethods, sql: string): string {
|
|
76
264
|
const token = sql.trim().split(/\s+/, 1)[0]?.toUpperCase();
|
|
77
265
|
if (token) return token;
|
|
@@ -112,6 +300,8 @@ export class SqlExecutor<
|
|
|
112
300
|
throw e;
|
|
113
301
|
}
|
|
114
302
|
|
|
303
|
+
this.assignMissingPostPrimaryUuids();
|
|
304
|
+
|
|
115
305
|
const logContext = getLogContext(this.config, this.request);
|
|
116
306
|
logWithLevel(
|
|
117
307
|
LogLevel.DEBUG,
|
|
@@ -549,6 +739,9 @@ export class SqlExecutor<
|
|
|
549
739
|
const logContext = getLogContext(this.config, this.request);
|
|
550
740
|
const cacheResults = method === C6C.GET
|
|
551
741
|
&& (this.request.cacheResults ?? true);
|
|
742
|
+
const cacheAllowListStatus: SqlAllowListStatus = this.config.sqlAllowListPath
|
|
743
|
+
? "allowed"
|
|
744
|
+
: "not verified";
|
|
552
745
|
|
|
553
746
|
const cacheRequestData = cacheResults
|
|
554
747
|
? JSON.parse(JSON.stringify(this.request ?? {}))
|
|
@@ -560,7 +753,13 @@ export class SqlExecutor<
|
|
|
560
753
|
|
|
561
754
|
const evictFromCache =
|
|
562
755
|
method === C6C.GET && cacheResults && cacheRequestData
|
|
563
|
-
? () => evictCacheEntry(
|
|
756
|
+
? () => evictCacheEntry(
|
|
757
|
+
method,
|
|
758
|
+
tableName,
|
|
759
|
+
cacheRequestData,
|
|
760
|
+
logContext,
|
|
761
|
+
cacheAllowListStatus,
|
|
762
|
+
)
|
|
564
763
|
: undefined;
|
|
565
764
|
|
|
566
765
|
if (cacheResults) {
|
|
@@ -568,7 +767,8 @@ export class SqlExecutor<
|
|
|
568
767
|
method,
|
|
569
768
|
tableName,
|
|
570
769
|
cacheRequestData,
|
|
571
|
-
logContext
|
|
770
|
+
logContext,
|
|
771
|
+
cacheAllowListStatus,
|
|
572
772
|
);
|
|
573
773
|
if (cachedRequest) {
|
|
574
774
|
const cachedData = (await cachedRequest).data;
|
|
@@ -611,12 +811,14 @@ export class SqlExecutor<
|
|
|
611
811
|
setCache(method, tableName, cacheRequestData, {
|
|
612
812
|
requestArgumentsSerialized,
|
|
613
813
|
request: cacheRequest,
|
|
814
|
+
allowListStatus: cacheAllowListStatus,
|
|
614
815
|
});
|
|
615
816
|
|
|
616
817
|
const cacheResponse = await cacheRequest;
|
|
617
818
|
setCache(method, tableName, cacheRequestData, {
|
|
618
819
|
requestArgumentsSerialized,
|
|
619
820
|
request: cacheRequest,
|
|
821
|
+
allowListStatus: cacheAllowListStatus,
|
|
620
822
|
response: cacheResponse,
|
|
621
823
|
final: true,
|
|
622
824
|
});
|
|
@@ -695,7 +897,9 @@ export class SqlExecutor<
|
|
|
695
897
|
return {
|
|
696
898
|
affected: result.affectedRows as number,
|
|
697
899
|
insertId: result.insertId as number,
|
|
698
|
-
rest:
|
|
900
|
+
rest: method === C6C.POST
|
|
901
|
+
? this.buildPostResponseRows(result.insertId as number | string | undefined)
|
|
902
|
+
: [],
|
|
699
903
|
sql: { sql: sqlExecution.sql, values: sqlExecution.values },
|
|
700
904
|
} as DetermineResponseDataType<G['RequestMethod'], G['RestTableInterface']>;
|
|
701
905
|
}
|
|
@@ -116,6 +116,7 @@ export interface iCacheResponse<ResponseDataType = any> {
|
|
|
116
116
|
export interface iCacheAPI<ResponseDataType = any> {
|
|
117
117
|
requestArgumentsSerialized: string;
|
|
118
118
|
request: Promise<iCacheResponse<ResponseDataType>>;
|
|
119
|
+
allowListStatus?: "allowed" | "denied" | "not verified";
|
|
119
120
|
response?: iCacheResponse<ResponseDataType> & {
|
|
120
121
|
__carbonTiming?: {
|
|
121
122
|
start: number;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type {iCacheAPI, iCacheResponse} from "../types/ormInterfaces";
|
|
2
2
|
import {LogContext, LogLevel, logWithLevel, shouldLog} from "./logLevel";
|
|
3
|
-
import logSql from "./logSql";
|
|
3
|
+
import logSql, { SqlAllowListStatus } from "./logSql";
|
|
4
4
|
|
|
5
5
|
// -----------------------------------------------------------------------------
|
|
6
6
|
// Cache Storage
|
|
@@ -61,13 +61,13 @@ export function checkCache<ResponseDataType = any>(
|
|
|
61
61
|
method: string,
|
|
62
62
|
tableName: string | string[],
|
|
63
63
|
requestData: any,
|
|
64
|
-
logContext
|
|
64
|
+
logContext?: LogContext,
|
|
65
|
+
allowListStatus?: SqlAllowListStatus,
|
|
65
66
|
): Promise<iCacheResponse<ResponseDataType>> | false {
|
|
66
67
|
const key = makeCacheKey(method, tableName, requestData);
|
|
67
68
|
const cached = apiRequestCache.get(key);
|
|
68
69
|
|
|
69
70
|
if (!cached) {
|
|
70
|
-
console.log('apiRequestCache.size', apiRequestCache.size)
|
|
71
71
|
return false;
|
|
72
72
|
}
|
|
73
73
|
|
|
@@ -75,7 +75,7 @@ export function checkCache<ResponseDataType = any>(
|
|
|
75
75
|
const sql = cached.response?.data?.sql?.sql ?? "";
|
|
76
76
|
const sqlMethod = sql.trim().split(/\s+/, 1)[0]?.toUpperCase() || method;
|
|
77
77
|
logSql({
|
|
78
|
-
allowListStatus: "not verified",
|
|
78
|
+
allowListStatus: cached.allowListStatus ?? allowListStatus ?? "not verified",
|
|
79
79
|
cacheStatus: "hit",
|
|
80
80
|
context: logContext,
|
|
81
81
|
method: sqlMethod,
|
|
@@ -103,7 +103,24 @@ export function evictCacheEntry(
|
|
|
103
103
|
method: string,
|
|
104
104
|
tableName: string | string[],
|
|
105
105
|
requestData: any,
|
|
106
|
+
logContext?: LogContext,
|
|
107
|
+
allowListStatus?: SqlAllowListStatus,
|
|
106
108
|
): boolean {
|
|
107
109
|
const key = makeCacheKey(method, tableName, requestData);
|
|
108
|
-
|
|
110
|
+
const cached = apiRequestCache.get(key);
|
|
111
|
+
const deleted = apiRequestCache.delete(key);
|
|
112
|
+
|
|
113
|
+
if (deleted && shouldLog(LogLevel.INFO, logContext)) {
|
|
114
|
+
const sql = cached?.response?.data?.sql?.sql ?? "";
|
|
115
|
+
const sqlMethod = sql.trim().split(/\s+/, 1)[0]?.toUpperCase() || method;
|
|
116
|
+
logSql({
|
|
117
|
+
allowListStatus: cached?.allowListStatus ?? allowListStatus ?? "not verified",
|
|
118
|
+
cacheStatus: "evicted",
|
|
119
|
+
context: logContext,
|
|
120
|
+
method: sqlMethod,
|
|
121
|
+
sql,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return deleted;
|
|
109
126
|
}
|
package/src/utils/logSql.ts
CHANGED
|
@@ -6,7 +6,7 @@ import type { LogContext } from "./logLevel";
|
|
|
6
6
|
import { LogLevel, shouldLog } from "./logLevel";
|
|
7
7
|
|
|
8
8
|
export type SqlAllowListStatus = "allowed" | "denied" | "not verified";
|
|
9
|
-
export type SqlCacheStatus = "hit" | "miss" | "ignored";
|
|
9
|
+
export type SqlCacheStatus = "hit" | "miss" | "ignored" | "evicted";
|
|
10
10
|
|
|
11
11
|
export type LogSqlContextOptions = {
|
|
12
12
|
cacheStatus: SqlCacheStatus;
|
|
@@ -66,6 +66,8 @@ const cacheLabel = (cacheStatus: SqlCacheStatus): string => {
|
|
|
66
66
|
switch (cacheStatus) {
|
|
67
67
|
case "hit":
|
|
68
68
|
return `${C.METHOD_COLORS.SELECT}[CACHE HIT]${C.RESET}`;
|
|
69
|
+
case "evicted":
|
|
70
|
+
return `${C.WARN}[CACHE EVICTED]${C.RESET}`;
|
|
69
71
|
case "ignored":
|
|
70
72
|
return `${C.WARN}[CACHE IGNORED]${C.RESET}`;
|
|
71
73
|
default:
|